From b4c2d2add2b863060ce559e49b3953f24050f162 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 14:47:44 -0500
Subject: can add strokes to mobile ink

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

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 73dc7edc6..d8b575092 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,7 +53,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
+            console.log("create drop", ele);
             if (ele) {
+                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
@@ -135,7 +137,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
-
         }
 
         @undoBatch
-- 
cgit v1.2.3-70-g09d2


From 3cca58612cde96a3082ca8e190fe2166d531d556 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 17:40:50 -0500
Subject: drawn strokes get added to mobile ink collection

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +--
 src/mobile/MobileInterface.scss                    |  7 +++
 src/mobile/MobileInterface.tsx                     | 62 ++++++++++++++--------
 .../authentication/models/current_user_utils.ts    |  2 +-
 6 files changed, 53 insertions(+), 30 deletions(-)
 create mode 100644 src/mobile/MobileInterface.scss

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 632f2f0d6..4d487c032 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -231,9 +231,9 @@ export default class GestureOverlay extends Touchable {
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+                {this.currentStroke}
                 {this.props.children}
                 {this._palette}
-                {this.currentStroke}
             </div>);
     }
 }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d8b575092..e94f24f2c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,9 +53,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
-            console.log("create drop", ele);
             if (ele) {
-                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c3e131184..8d376fb57 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -114,6 +114,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     private addDocument = (newBox: Doc) => {
         const added = this.props.addDocument(newBox);
+        console.log("adding doc from freeform", added);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -356,12 +357,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
-        console.log("on gesture");
         switch (ge.gesture) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
+                console.log("make stroke", inkDoc);
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -403,14 +404,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // I think it makes sense for the marquee menu to go away when panned. -syip2
-        MarqueeOptionsMenu.Instance.fadeOut(true);
+        MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
 
         let x = this.Document.panX || 0;
         let y = this.Document.panY || 0;
         const docs = this.childLayoutPairs.map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay) {
-            PDFMenu.Instance.fadeOut(true);
+            PDFMenu.Instance && PDFMenu.Instance.fadeOut(true);
             const minx = docs.length ? NumCast(docs[0].x) : 0;
             const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
             const miny = docs.length ? NumCast(docs[0].y) : 0;
@@ -990,6 +991,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
+        console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight());
         return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
new file mode 100644
index 000000000..e4cc919a5
--- /dev/null
+++ b/src/mobile/MobileInterface.scss
@@ -0,0 +1,7 @@
+.mobileInterface-topButtons {
+    position: absolute;
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    z-index: 9999;
+}
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 8d342d749..3cb08afa7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,10 +10,16 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
 import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
 import GestureOverlay from '../client/views/GestureOverlay';
+import { InkingControl } from '../client/views/InkingControl';
+import { InkTool } from '../new_fields/InkField';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import "./MobileInterface.scss";
+
+library.add(faTrash, faCheck);
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -22,6 +28,9 @@ export default class MobileInterface extends React.Component {
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
     @observable private currentView: "main" | "ink" | "library" = "main";
 
+    private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+    private inkDoc?: Doc;
+
     constructor(props: Readonly<{}>) {
         super(props);
         MobileInterface.Instance = this;
@@ -32,8 +41,8 @@ export default class MobileInterface extends React.Component {
         library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
 
         if (this.userDoc && !this.mainContainer) {
-            const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-            this.userDoc.activeMobile = doc;
+            // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+            this.userDoc.activeMobile = this.mainDoc;
         }
     }
 
@@ -44,13 +53,14 @@ export default class MobileInterface extends React.Component {
         if (this.userDoc) {
             switch (view) {
                 case "main": {
-                    const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.mainDoc;
                     break;
                 }
                 case "ink": {
-                    const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.inkDoc;
+                    InkingControl.Instance.switchTool(InkTool.Pen);
                     break;
                 }
             }
@@ -64,7 +74,7 @@ export default class MobileInterface extends React.Component {
                 Document={this.mainContainer}
                 DataDoc={undefined}
                 LibraryPath={emptyPath}
-                addDocument={undefined}
+                addDocument={returnFalse}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
@@ -89,40 +99,46 @@ export default class MobileInterface extends React.Component {
         return "hello";
     }
 
+    onClick = (e: React.MouseEvent) => {
+        // insert ink as collection into active displayed doc view
+        // reset ink collection
+        this.inkDoc = undefined;
+        console.log("clicked");
+        this.switchCurrentView("main");
+        InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+    }
+
     @computed
     get inkContent() {
-        // return <div>INK</div>;
+        // TODO: get props from active collection and pass to the new freeform
         if (this.mainContainer) {
             return (
                 <GestureOverlay>
-                    <CollectionFreeFormView
+                    <div className="mobileInterface-topButtons">
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Cancel drawing"><FontAwesomeIcon icon="trash" /></button>
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Insert drawing"><FontAwesomeIcon icon="check" /></button>
+                    </div>
+                    <CollectionView
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
                         fieldKey={""}
-                        addDocument={returnFalse}
-                        removeDocument={returnFalse}
-                        moveDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        PanelHeight={() => window.screen.height}
-                        PanelWidth={() => window.screen.width}
-                        annotationsKey={""}
-                        isAnnotationOverlay={false}
+                        PanelHeight={() => window.innerHeight}
+                        PanelWidth={() => window.innerWidth}
                         focus={emptyFunction}
-                        isSelected={returnTrue} //
+                        isSelected={returnTrue}
                         select={emptyFunction}
-                        active={returnTrue} //
+                        active={returnTrue}
                         ContentScaling={returnOne}
                         whenActiveChanged={returnFalse}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={Transform.Identity}
                         ruleProvider={undefined}
                         renderDepth={0}
                         ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        chromeCollapsed={true}>
-                    </CollectionFreeFormView>
+                        ContainingCollectionDoc={undefined}>
+                    </CollectionView>
                 </GestureOverlay>
             );
         }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b40179fbc..98092ddfe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -141,7 +141,7 @@ export class CurrentUserUtils {
     }
 
     static setupMobileInkingDoc(userDoc: Doc) {
-        return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" });
+        return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" });
     }
 
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
-- 
cgit v1.2.3-70-g09d2


From e8fcbbf57b2a2f443d9c280ce10558cf9d51c632 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Sun, 9 Feb 2020 17:11:24 -0500
Subject: can upload collection from mobile to desktop

---
 src/client/DocServer.ts                            |   9 +-
 src/client/util/DragManager.ts                     |   2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   1 +
 .../views/collections/CollectionStackingView.tsx   |   2 +
 .../CollectionStackingViewFieldColumn.tsx          |   1 +
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/CollectionViewChromes.tsx    |   1 +
 src/mobile/ImageUpload.tsx                         |   1 +
 src/mobile/MobileInkOverlay.tsx                    |  53 +++++++-
 src/mobile/MobileInterface.scss                    |   8 ++
 src/mobile/MobileInterface.tsx                     | 150 ++++++++++++---------
 src/server/ApiManagers/UploadManager.ts            |   1 +
 src/server/Message.ts                              |   6 +
 src/server/Websocket/Websocket.ts                  |   7 +-
 .../authentication/models/current_user_utils.ts    |  21 +--
 src/server/server_Initialization.ts                |  24 ++--
 17 files changed, 195 insertions(+), 96 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index c03764471..e09251855 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,5 +1,5 @@
 import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
 import { Opt, Doc } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
@@ -81,6 +81,10 @@ export namespace DocServer {
             Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
         }
 
+        export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) {
+            Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content);
+        }
+
     }
 
     export function init(protocol: string, hostname: string, port: number, identifier: string) {
@@ -116,6 +120,9 @@ export namespace DocServer {
         _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => {
             MobileInkOverlay.Instance.updatePosition(content);
         });
+        _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => {
+            MobileInkOverlay.Instance.uploadDocument(content);
+        });
     }
 
     function errorFunc(): never {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index df2f5fe3c..0bb8b531d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -179,7 +179,7 @@ export namespace DragManager {
             );
         }
         element.dataset.canDrop = "true";
-        const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+        const handler = (e: Event) => { console.log("drop target reveied docs"); dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
         element.addEventListener("dashOnDrop", handler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 80752303c..26e0cc35a 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -73,6 +73,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
 
     @undoBatch
     rowDrop = action((e: Event, de: DragManager.DropEvent) => {
+        console.log("masronry row drop");
         this._createAliasSelected = false;
         if (de.complete.docDragData) {
             (this.props.parent.Document.dropConverter instanceof ScriptField) &&
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 992820fc7..83c90810e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -240,6 +240,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
+        console.log("DROP STACKIN G2");
         const where = [de.x, de.y];
         let targInd = -1;
         let plusOne = 0;
@@ -270,6 +271,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     onDrop = async (e: React.DragEvent): Promise<void> => {
+        console.log("DROP STACKING");
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 39b4e4e1d..65c4b3195 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -57,6 +57,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @undoBatch
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
+        console.log("column drop stacking");
         this._createAliasSelected = false;
         if (de.complete.docDragData) {
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e94f24f2c..b35af2314 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 88023783b..1d399e26f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -138,7 +138,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
-        ContextMenu.Instance.clearItems();
+        ContextMenu.Instance && ContextMenu.Instance.clearItems();
         if (index !== -1) {
             value.splice(index, 1);
             return true;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index a870b6043..01dc21f95 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -231,6 +231,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @action
     toggleCollapse = () => {
+        console.log("toggle collapse");
         this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled";
         if (this.props.collapse) {
             this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled");
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 3304e8e22..10bd78075 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -47,6 +47,7 @@ class Uploader extends React.Component {
 
                     const upload = window.location.origin + "/upload";
                     this.status = "uploading image";
+                    console.log("uploading image", formData);
                     const res = await fetch(upload, {
                         method: 'POST',
                         body: formData
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 6b65aa436..ed4cca5b9 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,9 +1,13 @@
 import React = require('react');
 import { observer } from "mobx-react";
-import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent } from "../server/Message";
+import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
 import { observable, action } from "mobx";
 import { GestureUtils } from "../pen-gestures/GestureUtils";
 import "./MobileInkOverlay.scss";
+import { StrCast } from '../new_fields/Types';
+import { DragManager } from "../client/util/DragManager";
+import { DocServer } from '../client/DocServer';
+import { Doc } from '../new_fields/Doc';
 
 
 @observer
@@ -67,7 +71,7 @@ export default class MobileInkOverlay extends React.Component {
             height: bounds.height * this._scale,
         };
 
-        const target = document.elementFromPoint(points[0].X, points[0].Y);
+        const target = document.elementFromPoint(this._x + 10, this._y + 10);
         target?.dispatchEvent(
             new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
                 {
@@ -82,6 +86,43 @@ export default class MobileInkOverlay extends React.Component {
         );
     }
 
+    uploadDocument = async (content: MobileDocumentUploadContent) => {
+        const { docId } = content;
+        console.log("receive upload document id", docId);
+        const doc = await DocServer.GetRefField(docId);
+
+        if (doc && doc instanceof Doc) {
+            console.log("parsed upload document into doc", StrCast(doc.proto!.title));
+
+            const target = document.elementFromPoint(this._x + 10, this._y + 10);
+            console.log("the target is", target);
+
+            const dragData = new DragManager.DocumentDragData([doc]);
+            const complete = new DragManager.DragCompleteEvent(false, dragData);
+            console.log("the drag data is", dragData);
+
+            if (target) {
+                target.dispatchEvent(
+                    new CustomEvent<DragManager.DropEvent>("dashOnDrop",
+                        {
+                            bubbles: true,
+                            detail: {
+                                x: this._x,
+                                y: this._y,
+                                complete: complete,
+                                altKey: false,
+                                metaKey: false,
+                                ctrlKey: false
+                            }
+                        }
+                    )
+                );
+            } else {
+                alert("TARGET IS UNDEFINED");
+            }
+        }
+    }
+
     @action
     dragStart = (e: React.PointerEvent) => {
         document.removeEventListener("pointermove", this.dragging);
@@ -132,15 +173,17 @@ export default class MobileInkOverlay extends React.Component {
                     transform: `translate(${this._x}px, ${this._y}px)`,
                     zIndex: 30000,
                     pointerEvents: "none",
-                    borderStyle: this._isDragging ? "solid" : "dashed"
-                }}
+                    borderStyle: this._isDragging ? "solid" : "dashed",
+                    backgroundColor: "rgba(255, 0, 0, 0.3)"
+                }
+                }
                 ref={this._mainCont}
             >
                 <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
-            </div>
+            </div >
         );
     }
 }
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
index 8abe5a40d..8b0ebcd53 100644
--- a/src/mobile/MobileInterface.scss
+++ b/src/mobile/MobileInterface.scss
@@ -1,7 +1,15 @@
 .mobileInterface-inkInterfaceButtons {
     position: absolute;
+    top: -50px;
     display: flex;
     justify-content: space-between;
     width: 100%;
     z-index: 9999;
+    height: 50px;
+}
+
+.mobileInterface-container {
+    height: calc(100% - 50px);
+    margin-top: 50px;
+    position: relative;
 }
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 665d9a168..a1ef0a5d1 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -22,6 +22,18 @@ import { SelectionManager } from '../client/util/SelectionManager';
 import { DateField } from '../new_fields/DateField';
 import { GestureUtils } from '../pen-gestures/GestureUtils';
 import { DocServer } from '../client/DocServer';
+import { DocumentDecorations } from '../client/views/DocumentDecorations';
+import { OverlayView } from '../client/views/OverlayView';
+import { DictationOverlay } from '../client/views/DictationOverlay';
+import SharingManager from '../client/util/SharingManager';
+import { PreviewCursor } from '../client/views/PreviewCursor';
+import { ContextMenu } from '../client/views/ContextMenu';
+import { RadialMenu } from '../client/views/nodes/RadialMenu';
+import PDFMenu from '../client/views/pdf/PDFMenu';
+import MarqueeOptionsMenu from '../client/views/collections/collectionFreeForm/MarqueeOptionsMenu';
+import GoogleAuthenticationManager from '../client/apis/GoogleAuthenticationManager';
+import { listSpec } from '../new_fields/Schema';
+import { Id } from '../new_fields/FieldSymbols';
 
 library.add(faLongArrowAltLeft);
 
@@ -31,7 +43,7 @@ export default class MobileInterface extends React.Component {
     @computed private get userDoc() { return CurrentUserUtils.UserDocument; }
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
     // @observable private currentView: "main" | "ink" | "upload" = "main";
-    private mainDoc: Doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+    private mainDoc: any = CurrentUserUtils.setupMobileDoc(this.userDoc);
     @observable private renderView?: () => JSX.Element;
 
     // private inkDoc?: Doc;
@@ -49,7 +61,6 @@ export default class MobileInterface extends React.Component {
         library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
 
         if (this.userDoc && !this.mainContainer) {
-            // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
             this.userDoc.activeMobile = this.mainDoc;
         }
     }
@@ -76,48 +87,22 @@ export default class MobileInterface extends React.Component {
         });
     }
 
-    // @action
-    // switchCurrentView = (view: "main" | "ink" | "upload") => {
-    //     this.currentView = view;
-
-    //     if (this.userDoc) {
-    //         switch (view) {
-    //             case "main": {
-    //                 // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-    //                 this.userDoc.activeMobile = this.mainDoc;
-    //                 break;
-    //             }
-    //             case "ink": {
-    //                 this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
-    //                 this.userDoc.activeMobile = this.inkDoc;
-    //                 InkingControl.Instance.switchTool(InkTool.Pen);
-    //                 this.drawingInk = true;
-
-    //                 DocServer.Mobile.dispatchOverlayTrigger({
-    //                     enableOverlay: true,
-    //                     width: window.innerWidth,
-    //                     height: window.innerHeight
-    //                 });
-
-    //                 break;
-    //             }
-    //             case "upload": {
-    //                 this.uploadDoc = CurrentUserUtils.setupMobileUploadDoc(this.userDoc);
-    //                 this.userDoc.activeMobile = this.uploadDoc;
-
-    //             }
-    //         }
-    //     }
-    // }
+    onSwitchUpload = () => {
+        DocServer.Mobile.dispatchOverlayTrigger({
+            enableOverlay: true,
+            width: 100,
+            height: 100
+        });
+    }
 
     renderDefaultContent = () => {
-        console.log("rendering default content");
+        console.log("rendering default content", this.mainContainer);
         if (this.mainContainer) {
             return <DocumentView
                 Document={this.mainContainer}
                 DataDoc={undefined}
                 LibraryPath={emptyPath}
-                addDocument={returnFalse}
+                addDocument={(doc: Doc) => { console.log("want to add doc to default content", StrCast(doc.title)); return false; }}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
@@ -192,42 +177,54 @@ export default class MobileInterface extends React.Component {
                             <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
                         </div>
                     </div>
-                    <GestureOverlay>
-                        <CollectionView
-                            Document={this.mainContainer}
-                            DataDoc={undefined}
-                            LibraryPath={emptyPath}
-                            fieldKey={""}
-                            addDocTab={returnFalse}
-                            pinToPres={emptyFunction}
-                            PanelHeight={() => window.innerHeight}
-                            PanelWidth={() => window.innerWidth}
-                            focus={emptyFunction}
-                            isSelected={returnFalse}
-                            select={emptyFunction}
-                            active={returnFalse}
-                            ContentScaling={returnOne}
-                            whenActiveChanged={returnFalse}
-                            ScreenToLocalTransform={Transform.Identity}
-                            ruleProvider={undefined}
-                            renderDepth={0}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}>
-                        </CollectionView>
-                    </GestureOverlay>
+                    <CollectionView
+                        Document={this.mainContainer}
+                        DataDoc={undefined}
+                        LibraryPath={emptyPath}
+                        fieldKey={""}
+                        addDocTab={returnFalse}
+                        pinToPres={emptyFunction}
+                        PanelHeight={() => window.innerHeight}
+                        PanelWidth={() => window.innerWidth}
+                        focus={emptyFunction}
+                        isSelected={returnFalse}
+                        select={emptyFunction}
+                        active={returnFalse}
+                        ContentScaling={returnOne}
+                        whenActiveChanged={returnFalse}
+                        ScreenToLocalTransform={Transform.Identity}
+                        ruleProvider={undefined}
+                        renderDepth={0}
+                        ContainingCollectionView={undefined}
+                        ContainingCollectionDoc={undefined}>
+                    </CollectionView>
                 </div>
             );
         }
     }
 
-    upload = () => {
+    upload = async (e: React.MouseEvent) => {
+        if (this.mainContainer) {
+            const data = Cast(this.mainContainer.data, listSpec(Doc));
+            if (data) {
+                const uploadDoc = await data[1]; // TODO: ensure this is the collection to upload
 
+                console.log("UPLOADING DOCUMENT FROM MOBILE", uploadDoc[Id], StrCast(uploadDoc.proto!.title));
+                if (uploadDoc) {
+                    DocServer.Mobile.dispatchMobileDocumentUpload({
+                        docId: uploadDoc[Id]
+                    });
+                }
+            }
+        }
+        e.stopPropagation();
+        e.preventDefault();
     }
 
     renderUploadContent() {
         if (this.mainContainer) {
             return (
-                <div className="mobileInterface">
+                <div className="mobileInterface" onDragOver={this.onDragOver}>
                     <div className="mobileInterface-inkInterfaceButtons">
                         <div className="navButtons">
                             <button className="mobileInterface-button cancel" onClick={this.onBack} title="Back">BACK</button>
@@ -240,7 +237,7 @@ export default class MobileInterface extends React.Component {
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
-                        addDocument={returnFalse}
+                        addDocument={(doc: Doc) => { console.log("want to add doc", StrCast(doc.title)); return false; }}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
                         removeDocument={undefined}
@@ -266,13 +263,35 @@ export default class MobileInterface extends React.Component {
         }
     }
 
+    onDragOver = (e: React.DragEvent) => {
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
     render() {
         // const content = this.currentView === "main" ? this.mainContent :
         //     this.currentView === "ink" ? this.inkContent :
         //         this.currentView === "upload" ? this.uploadContent : <></>;
         return (
-            <div className="mobile-container">
-                {this.renderView ? this.renderView() : this.renderDefaultContent()}
+            <div className="mobileInterface-container" onDragOver={this.onDragOver}>
+                {/* <DocumentDecorations />
+                <GestureOverlay>
+                    {this.renderView ? this.renderView() : this.renderDefaultContent()}
+                </GestureOverlay> */}
+
+                {/* <DictationOverlay />
+                <SharingManager />
+                <GoogleAuthenticationManager /> */}
+                <DocumentDecorations />
+                <GestureOverlay>
+                    {this.renderView ? this.renderView() : this.renderDefaultContent()}
+                </GestureOverlay>
+                <PreviewCursor />
+                {/* <ContextMenu /> */}
+                <RadialMenu />
+                {/* <PDFMenu />
+                <MarqueeOptionsMenu />
+                <OverlayView /> */}
             </div>
         );
     }
@@ -281,5 +300,6 @@ export default class MobileInterface extends React.Component {
 Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); });
 Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); });
 Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); });
+Scripting.addGlobal(function onSwitchMobileUpload() { return MobileInterface.Instance.onSwitchUpload(); });
 Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); });
 
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 74f45ae62..e76d9b7a2 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -42,6 +42,7 @@ export default class UploadManager extends ApiManager {
             method: Method.POST,
             subscription: "/upload",
             secureHandler: async ({ req, res }) => {
+                console.log("/upload register");
                 const form = new formidable.IncomingForm();
                 form.uploadDir = pathToDirectory(Directory.parsed_files);
                 form.keepExtensions = true;
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 236df3f3c..c23a2f0a8 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,5 +1,6 @@
 import { Utils } from "../Utils";
 import { Point } from "../pen-gestures/ndollar";
+import { Doc } from "../new_fields/Doc";
 
 export class Message<T> {
     private _name: string;
@@ -62,6 +63,10 @@ export interface UpdateMobileInkOverlayPositionContent {
     readonly dsize?: number;
 }
 
+export interface MobileDocumentUploadContent {
+    readonly docId: string;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -75,6 +80,7 @@ export namespace MessageStore {
     export const GesturePoints = new Message<GestureContent>("Gesture Points");
     export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
     export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>("Update Mobile Ink Overlay Position");
+    export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>("Upload Document From Mobile");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 77816c897..798bdae67 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
 import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../Message";
 import { Client } from "../Client";
 import { Socket } from "socket.io";
 import { Database } from "../database";
@@ -57,6 +57,7 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
             Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
             Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
+            Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 
@@ -83,6 +84,10 @@ export namespace WebSocket {
         socket.broadcast.emit("receiveUpdateOverlayPosition", content);
     }
 
+    function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
+        socket.broadcast.emit("receiveMobileDocumentUpload", content);
+    }
+
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
         const { ProjectCredentials } = GoogleCredentialsLoader;
         switch (query.type) {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 817cf40b1..3e5953ac1 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -103,7 +103,7 @@ export class CurrentUserUtils {
             { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
             { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc },
-            { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload);', backgroundColor: "orange" },
+            { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" },
             { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" },
         ];
         return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
@@ -138,7 +138,9 @@ export class CurrentUserUtils {
     }
 
     static setupMobileDoc(userDoc: Doc) {
-        return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
+        const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" });
+
+        return userDoc.activeMoble ?? Docs.Create.MasonryDocument([webDoc, ...CurrentUserUtils.setupMobileButtons(userDoc)], {
             columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
         });
     }
@@ -148,13 +150,14 @@ export class CurrentUserUtils {
     }
 
     static setupMobileUploadDoc(userDoc: Doc) {
-        console.log("setup mobile upload", window.innerWidth, window.innerHeight);
-        const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" });
-        const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload", backgroundColor: "pink" });
-        return Docs.Create.StackingDocument([webDoc, uploadDoc], {
-            title: "Mobile Upload", backgroundColor: "white",
-            columnWidth: window.innerWidth, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", autoHeight: true, yMargin: 5,
-            width: window.innerWidth, height: window.innerHeight
+        const webDoc = Docs.Create.WebDocument("https://yahoo.com", { title: "Upload Images From the Web", chromeStatus: "enabled" });
+        const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload Collection", backgroundColor: "pink" });
+        console.log("window size", window.innerWidth, window.innerHeight);
+        // return Docs.Create.StackingDocument([webDoc, uploadDoc], {
+        //     columnWidth: window.innerWidth, //ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "Mobile Upload", autoHeight: true, yMargin: 5
+        // });
+        return Docs.Create.StackingDocument([webDoc, uploadDoc], {//...CurrentUserUtils.setupMobileButtons(userDoc)], {
+            columnWidth: 100, lockedPosition: true, chromeStatus: "disabled", title: "Upload", autoHeight: true, yMargin: 30
         });
     }
 
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index cbe070293..5f1ecc733 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -42,18 +42,18 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
         }
     };
     app.use(cors(corsOptions));
-    app.use("*", ({ user, originalUrl }, res, next) => {
-        if (user && !originalUrl.includes("Heartbeat")) {
-            const userEmail = (user as any).email;
-            if (userEmail) {
-                timeMap[userEmail] = Date.now();
-            }
-        }
-        if (!user && originalUrl === "/") {
-            return res.redirect("/login");
-        }
-        next();
-    });
+    // app.use("*", ({ user, originalUrl }, res, next) => {
+    //     if (user && !originalUrl.includes("Heartbeat")) {
+    //         const userEmail = (user as any).email;
+    //         if (userEmail) {
+    //             timeMap[userEmail] = Date.now();
+    //         }
+    //     }
+    //     if (!user && originalUrl === "/") {
+    //         return res.redirect("/login");
+    //     }
+    //     next();
+    // });
 
     app.use(wdm(compiler, { publicPath: config.output.publicPath }));
     app.use(whm(compiler));
-- 
cgit v1.2.3-70-g09d2


From dba26063754e6a882c8a3eb238b04a935989f406 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 10 Feb 2020 04:52:04 -0500
Subject: started long press for iframe to copy image

---
 src/client/util/DragManager.ts                     |   6 +-
 src/client/views/collections/CollectionSubView.tsx |   2 +
 src/client/views/nodes/WebBox.tsx                  | 114 ++++++++++++++++++++-
 3 files changed, 119 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0bb8b531d..745540ff9 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -179,7 +179,7 @@ export namespace DragManager {
             );
         }
         element.dataset.canDrop = "true";
-        const handler = (e: Event) => { console.log("drop target reveied docs"); dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
+        const handler = (e: Event) => { dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
         element.addEventListener("dashOnDrop", handler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
@@ -265,6 +265,10 @@ export namespace DragManager {
         StartDrag([ele], dragData, downX, downY, options);
     }
 
+    export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) {
+        StartDrag([ele], {}, downX, downY);
+    }
+
     function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
         eles = eles.filter(e => e);
         if (!dragDiv) {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b35af2314..b5067ea9e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -180,6 +180,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
             const html = e.dataTransfer.getData("text/html");
             const text = e.dataTransfer.getData("text/plain");
+            console.log(html);
 
             if (text && text.startsWith("<div")) {
                 return;
@@ -212,6 +213,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
                 if (img) {
                     const split = img.split("src=\"")[1].split("\"")[0];
+                    console.log("creating image", split);
                     const doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
                     ImageUtils.ExtractExif(doc);
                     this.props.addDocument(doc);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 0b23c3bec..c405dd9d4 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -21,6 +21,9 @@ import "./WebBox.scss";
 import React = require("react");
 import { DocAnnotatableComponent } from "../DocComponent";
 import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { DragManager } from "../../util/DragManager";
+import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 
 library.add(faStickyNote);
 
@@ -34,6 +37,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
+    private _longPressSecondsHack?: NodeJS.Timeout;
+    private _iframeRef = React.createRef<HTMLIFrameElement>();
+    private _iframeDragRef = React.createRef<HTMLDivElement>();
+    @observable private _pressX: number = 0;
+    @observable private _pressY: number = 0;
+
     componentWillMount() {
 
         const field = Cast(this.props.Document[this.props.fieldKey], WebField);
@@ -51,6 +60,49 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         this.setURL();
     }
 
+    componentDidMount() {
+        document.addEventListener("pointerup", this.onLongPressUp);
+        document.addEventListener("pointermove", this.onLongPressMove);
+        // this._iframeRef.current?.contentWindow?.document.addEventListener("mousedown", (event: MouseEvent) => {
+        //     console.log("clicked inside the iframe?");
+        // });
+        // const iframe = document.getElementById(this.props.Document.proto![Id]);
+        // if (iframe) {
+        //     iframe.addEventListener('pointerdown', function (event) {
+        //         const B = iframe.getBoundingClientRect();
+        //         const e = new CustomEvent('pointerdown', { bubbles: true, cancelable: false });
+        //         // e.clientX = e.clientX + B?.left;
+        //         // e.clientY = e.clientY + B?.top;
+        //         console.log("custom event pointer down");
+        //         iframe.dispatchEvent(e);
+        //     })
+        // }
+        // if (this._iframeRef.current) {
+        //     console.log("resetting iframes events");
+        //     const self = this;
+        //     this._iframeRef.current.addEventListener('pointermove', function (event) {
+        //         const B = self._iframeRef.current?.getBoundingClientRect();
+        //         const e = new CustomEvent('pointermove', { bubbles: true, cancelable: false });
+        //         // e.clientX = e.clientX + B?.left;
+        //         // e.clientY = e.clientY + B?.top;
+        //         self._iframeRef.current?.dispatchEvent(e);
+        //     });
+        //     this._iframeRef.current.addEventListener('pointerdown', function (event) {
+        //         const B = self._iframeRef.current?.getBoundingClientRect();
+        //         const e = new CustomEvent('pointerdown', { bubbles: true, cancelable: false });
+        //         // e.clientX = e.clientX + B?.left;
+        //         // e.clientY = e.clientY + B?.top;
+        //         console.log("custom event pointer down");
+        //         self._iframeRef.current?.dispatchEvent(e);
+        //     })
+        // }
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerup", this.onLongPressUp);
+        document.removeEventListener("pointermove", this.onLongPressMove);
+    }
+
     @action
     onURLChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.url = e.target.value;
@@ -165,6 +217,62 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         }
     }
 
+    // TODO: make this actually a long press
+    onLongPressDown = (e: React.PointerEvent) => {
+
+        console.log("press down", e.clientX, e.clientX);
+        this._pressX = e.clientX;
+        this._pressY = e.clientY;
+        this._longPressSecondsHack = setTimeout(() => {
+            console.log("start the drag!!");
+            const B = this._iframeRef.current?.getBoundingClientRect();
+            const iframeDoc = this._iframeRef.current?.contentDocument;
+            if (B && iframeDoc) {
+                console.log("frame doc", iframeDoc);
+                console.log("get point", this._pressX, B.left, this._pressY, B.top);
+                const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top);
+                console.log("found element", element);
+                if (element) {
+                    e.stopPropagation();
+                    e.preventDefault();
+                    const clone = element.cloneNode(true) as HTMLElement;
+
+                    if (clone.nodeName === "IMG") {
+                        const src = clone.getAttribute("src"); // TODO: may not always work
+
+                        if (src) {
+                            const doc = Docs.Create.ImageDocument(src, { width: 300 });
+                            ImageUtils.ExtractExif(doc);
+
+                            console.log("start image drag", this._pressX, this._pressY, doc);
+                            // document.dispatchEvent()
+                            const dragData = new DragManager.DocumentDragData([doc]);
+                            DragManager.StartDocumentDrag([clone], dragData, this._pressX, this._pressY);
+                        }
+                    }
+                }
+            }
+        }, 1500);
+        // e.stopPropagation();
+        // e.preventDefault();
+    }
+
+    onLongPressMove = (e: PointerEvent) => {
+        this._pressX = e.clientX;
+        this._pressY = e.clientY;
+    }
+
+    onLongPressUp = (e: PointerEvent) => {
+        console.log("press up");
+        if (this._longPressSecondsHack) {
+            clearTimeout(this._longPressSecondsHack);
+            console.log("long press cancelled");
+        }
+        // e.stopPropagation();
+        // e.preventDefault();
+    }
+
+
     @computed
     get content() {
         const field = this.dataDoc[this.props.fieldKey];
@@ -172,9 +280,9 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         if (field instanceof HtmlField) {
             view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
         } else if (field instanceof WebField) {
-            view = <iframe src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+            view = <iframe ref={this._iframeRef} src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
         } else {
-            view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+            view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
         }
         const content =
             <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
@@ -192,7 +300,9 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 <div className={classname}  >
                     {content}
                 </div>
+                <div className="mobileIframeDragger" ref={this._iframeDragRef} draggable></div>
                 {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
+                {/* onPointerDown={(e) => { this.onPrePointer(e); this.onLongPressDown(e) }} onPointerMove={this.onPrePointer} onPointerUp={(e) => { this.onPrePointer(e); }} />} */}
             </>);
     }
     render() {
-- 
cgit v1.2.3-70-g09d2


From 60a008f635177acccaa9dacb4006491725c93702 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 10 Feb 2020 15:05:30 -0500
Subject: small cleanups to adding stacking views to contentfittingboxes (like
 time/pivot).  needs to have nativeWidth/Height set.

---
 src/client/documents/Documents.ts                  |  4 +++
 src/client/util/RichTextRules.ts                   | 19 ++++++------
 src/client/util/RichTextSchema.tsx                 | 18 +++++------
 .../views/collections/CollectionStackingView.tsx   | 35 +++++++++-------------
 src/client/views/collections/CollectionSubView.tsx |  5 +++-
 .../views/collections/CollectionTimeView.tsx       |  3 +-
 src/client/views/collections/CollectionView.tsx    |  6 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 23 +++++++-------
 .../CollectionMulticolumnView.scss                 |  2 +-
 .../CollectionMultirowView.scss                    |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 20 ++++++++-----
 src/new_fields/Doc.ts                              | 17 +++++++++--
 .../authentication/models/current_user_utils.ts    |  6 ++--
 14 files changed, 87 insertions(+), 75 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6775d2302..29f253115 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -594,6 +594,10 @@ export namespace Docs {
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multicolumn });
         }
+        export function MultirowDocument(documents: Array<Doc>, options: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multirow });
+        }
+
 
         export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry });
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 3b30b5b3f..1b67b4fe4 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -1,17 +1,16 @@
-import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules";
-import { schema } from "./RichTextSchema";
-import { wrappingInputRule } from "./prosemirrorPatches";
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
 import { NodeSelection, TextSelection } from "prosemirror-state";
-import { StrCast, Cast, NumCast } from "../../new_fields/Types";
-import { Doc, DataSym } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Docs, DocUtils } from "../documents/Documents";
+import { DataSym, Doc } from "../../new_fields/Doc";
 import { Id } from "../../new_fields/FieldSymbols";
-import { DocServer } from "../DocServer";
+import { ComputedField } from "../../new_fields/ScriptField";
+import { Cast, NumCast } from "../../new_fields/Types";
 import { returnFalse, Utils } from "../../Utils";
+import { DocServer } from "../DocServer";
+import { Docs, DocUtils } from "../documents/Documents";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { wrappingInputRule } from "./prosemirrorPatches";
 import RichTextMenu from "./RichTextMenu";
-import { RichTextField } from "../../new_fields/RichTextField";
-import { ComputedField } from "../../new_fields/ScriptField";
+import { schema } from "./RichTextSchema";
 
 export const inpRules = {
     rules: [
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c07ebe2ed..043b277de 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,28 +1,26 @@
-import { reaction, IReactionDisposer, observable, runInAction } from "mobx";
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { baseKeymap, toggleMark } from "prosemirror-commands";
 import { redo, undo } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-state";
+import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
 import { StepMap } from "prosemirror-transform";
 import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
-import { Doc, WidthSym, HeightSym, DataSym, Field } from "../../new_fields/Doc";
+import { Doc, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/FieldSymbols";
+import { ObjectField } from "../../new_fields/ObjectField";
+import { ComputedField } from "../../new_fields/ScriptField";
+import { BoolCast, NumCast, StrCast } from "../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { DocumentView } from "../views/nodes/DocumentView";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
-import { BoolCast, NumCast, StrCast } from "../../new_fields/Types";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { observer } from "mobx-react";
-import { Id } from "../../new_fields/FieldSymbols";
-import { OnChangeHandler } from "react-color/lib/components/common/ColorWrap";
 
 const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d772ace23..21c34d047 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,25 +4,25 @@ import { CursorProperty } from "csstype";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils } from "../../../Utils";
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
+import { Utils } from "../../../Utils";
 import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
+import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
 import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
-import { TraceMobx } from "../../../new_fields/util";
 import { CollectionViewType } from "./CollectionView";
 
 @observer
@@ -55,7 +55,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
-            const width = () => this.widthScale * Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+            const width = () => Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
             const dref = React.createRef<HTMLDivElement>();
             const dxf = () => this.getDocTransform(d, dref.current!);
             this._docXfs.push({ dxf: dxf, width: width, height: height });
@@ -376,16 +376,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         }
         return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
     }
-    @computed get contentScale() {
-        const heightExtra = this.heightPercent > 1 ? this.heightPercent : 1;
-        return Math.min(this.props.Document[WidthSym]() / this.props.PanelWidth(), heightExtra * this.props.Document[HeightSym]() / this.props.PanelHeight());
-    }
-    @computed get widthScale() {
-        return this.heightPercent < 1 ? Math.max(1, this.contentScale) : 1;
-    }
-    @computed get heightPercent() {
-        return this.props.PanelHeight() / this.layoutDoc[HeightSym]();
-    }
+
+    @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); }
+
     render() {
         TraceMobx();
         const editableViewProps = {
@@ -399,9 +392,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ref={this.createRef}
                     style={{
                         overflowY: this.props.active() ? "auto" : "hidden",
-                        transform: `scale(${Math.min(1, this.heightPercent)})`,
-                        height: `${100 * Math.max(1, this.contentScale)}%`,
-                        width: `${100 * this.widthScale}%`,
+                        transform: `scale(${this.scaling}`,
+                        height: `${1 / this.scaling * 100}%`,
+                        width: `${1 / this.scaling * 100}%`,
                         transformOrigin: "top left",
                     }}
                     onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 62b9e8380..d71bad647 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
 import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
@@ -167,6 +167,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     return true;
                 }
                 let added = false;
+                if (this.props.Document._freezeOnDrop) {
+                    de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
+                }
                 if (docDragData.dropAction || docDragData.userDropAction) {
                     added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } else if (docDragData.moveDocument) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 6058f4e1d..db176d0bc 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -2,7 +2,7 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Field } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Field, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
@@ -31,6 +31,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _layoutEngine = "pivot";
 
     componentDidMount() {
+        this.props.Document._freezeOnDrop = true;
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         if (!this.props.Document._facetCollection) {
             const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2f27e5273..c081649cc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Doc, DocListCast, DataSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { listSpec } from '../../../new_fields/Schema';
 import { BoolCast, Cast, StrCast, NumCast } from '../../../new_fields/Types';
@@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document); // bcz: shouldn't this be Doc.Layout(this.props.Document)?  Right now, that causes problems with Buxton layout & adding things to a SLideView
+        const targetDataDoc = this.props.Document[DataSym];
         Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
@@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     removeDocument(doc: Doc): boolean {
         const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
-        const value = Cast(Doc.GetProto(this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
+        const value = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index baf09fe5b..da4dc0270 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -186,7 +186,7 @@ export function computePivotLayout(
 
     const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
     const dividers = sortedPivotKeys.map((key, i) =>
-        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c78a2a2cf..07a5a2c7b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,20 +1,22 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
+import { computedFn } from "mobx-utils";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
-import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
+import { InkTool } from "../../../../new_fields/InkField";
+import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
+import { TraceMobx } from "../../../../new_fields/util";
+import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
-import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
+import { Docs } from "../../../documents/Documents";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
@@ -32,15 +34,12 @@ import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData, ViewDefBounds } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { computedFn } from "mobx-utils";
-import { TraceMobx } from "../../../../new_fields/util";
-import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index 0c74b8ddb..821c8d804 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -1,8 +1,8 @@
 .collectionMulticolumnView_contents {
     display: flex;
+    overflow: hidden;
     width: 100%;
     height: 100%;
-    overflow: hidden;
 
     .document-wrapper {
         display: flex;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index 64f607680..a7e2c5707 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -1,8 +1,8 @@
 .collectionMultirowView_contents {
     display: flex;
+    overflow: hidden;
     width: 100%;
     height: 100%;
-    overflow: hidden;
     flex-direction: column;
 
     .document-wrapper {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1199ed7ee..080b01df2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -610,13 +610,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @undoBatch
-    @action
-    freezeNativeDimensions = (): void => {
-        this.layoutDoc._autoHeight = false;
-        this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect;
-        if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) {
-            this.layoutDoc._nativeWidth = this.props.PanelWidth();
-            this.layoutDoc._nativeHeight = this.props.PanelHeight();
+    public static unfreezeNativeDimensions = action((layoutDoc: Doc): void => {
+        layoutDoc._nativeWidth = undefined;
+        layoutDoc._nativeHeight = undefined;
+    });
+
+    toggleNativeDimensions = () => {
+        if (this.Document._nativeWidth || this.Document._nativeHeight) {
+            DocumentView.unfreezeNativeDimensions(this.layoutDoc);
+        }
+        else {
+            Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
         }
     }
 
@@ -730,7 +734,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
         layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        layoutItems.push({ description: this.Document.ignoreAspect || !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
+        layoutItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
         layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
         layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
         layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3baab119f..a28c6f58f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -14,7 +14,7 @@ import { ComputedField, ScriptField } from "./ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
 import { intersectRect } from "../Utils";
-import { UndoManager } from "../client/util/UndoManager";
+import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { computedFn } from "mobx-utils";
 import { RichTextField } from "./RichTextField";
 import { Script } from "vm";
@@ -178,7 +178,7 @@ export class Doc extends RefField {
     private [SelfProxy]: any;
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
-    public get [DataSym]() { return Cast(this[SelfProxy].resolvedDataDoc, Doc, null) || this[SelfProxy]; }
+    public get [DataSym]() { return Cast(Doc.Layout(this[SelfProxy]).resolvedDataDoc, Doc, null) || this[SelfProxy]; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
     @computed get __LAYOUT__() {
         const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
@@ -842,6 +842,17 @@ export namespace Doc {
             }
         }
     }
+
+    @undoBatch
+    @action
+    export function freezeNativeDimensions(layoutDoc: Doc, width: number, height: number): void {
+        layoutDoc._autoHeight = false;
+        layoutDoc.ignoreAspect = false;
+        if (!layoutDoc.ignoreAspect && !layoutDoc._nativeWidth) {
+            layoutDoc._nativeWidth = NumCast(layoutDoc._width, width);
+            layoutDoc._nativeHeight = NumCast(layoutDoc._height, height);
+        }
+    }
 }
 
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
@@ -867,4 +878,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     return docs.length ? new List(docs) : prevValue;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) { Doc.setDocFilter(container, key, value, modifiers); });
-Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index efee42f63..ea7a4999c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -234,12 +234,12 @@ export class CurrentUserUtils {
 
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
-        const slideTemplate = Docs.Create.StackingDocument(
+        const slideTemplate = Docs.Create.MultirowDocument(
             [
-                Docs.Create.MulticolumnDocument([], { title: "images", _height: 200, _xMargin: 10, _yMargin: 10 }),
+                Docs.Create.MulticolumnDocument([], { title: "images", _height: 200 }),
                 Docs.Create.TextDocument("", { title: "contents", _height: 100 })
             ],
-            { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", backgroundColor: "lightGray", _autoHeight: true });
+            { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, backgroundColor: "lightGray", _autoHeight: true });
         slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
 
         const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") });
-- 
cgit v1.2.3-70-g09d2


From 07fa40d04c7cc7b0006da69648c13ee54bcd4d27 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 11 Feb 2020 10:45:05 -0500
Subject: fixes for chrome for presBox

---
 package-lock.json                                  | 95 +++++++++++++---------
 src/client/views/MainView.tsx                      |  1 -
 src/client/views/TemplateMenu.tsx                  |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  1 -
 src/client/views/collections/CollectionView.tsx    | 12 +--
 .../CollectionMultirowView.tsx                     |  2 +
 src/client/views/nodes/FormattedTextBox.tsx        |  3 +-
 src/client/views/nodes/ImageBox.tsx                |  3 +-
 src/client/views/nodes/PresBox.tsx                 |  1 +
 src/client/views/nodes/VideoBox.tsx                |  3 +-
 src/client/views/nodes/WebBox.tsx                  |  3 +-
 src/client/views/pdf/PDFViewer.tsx                 |  3 +-
 src/new_fields/documentSchemas.ts                  | 20 +++--
 .../authentication/models/current_user_utils.ts    |  4 +-
 14 files changed, 85 insertions(+), 68 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index a09df33da..2636d7113 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -657,7 +657,7 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
                 "@types/express": "*"
@@ -1997,7 +1997,7 @@
                 },
                 "util": {
                     "version": "0.10.3",
-                    "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+                    "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
                     "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
                     "dev": true,
                     "requires": {
@@ -2585,7 +2585,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2619,7 +2619,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -2783,7 +2783,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3576,7 +3576,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3588,7 +3588,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4126,7 +4126,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -5406,7 +5406,8 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5424,11 +5425,13 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5441,15 +5444,18 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5552,7 +5558,8 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5562,6 +5569,7 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5574,17 +5582,20 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "minipass": {
                     "version": "2.9.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5601,6 +5612,7 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5681,7 +5693,8 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5691,6 +5704,7 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -5766,7 +5780,8 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -5796,6 +5811,7 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -5813,6 +5829,7 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -5851,11 +5868,13 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "yallist": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 }
             }
         },
@@ -6975,7 +6994,7 @@
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7030,7 +7049,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7717,7 +7736,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8035,7 +8054,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8067,7 +8086,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8242,7 +8261,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -8575,7 +8594,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -8658,7 +8677,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12258,7 +12277,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12271,7 +12290,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -12511,7 +12530,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -13906,7 +13925,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14348,7 +14367,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -14623,7 +14642,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -15480,7 +15499,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -15510,7 +15529,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -15526,7 +15545,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16298,7 +16317,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -17751,7 +17770,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a9e81093a..401a4b15c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -493,7 +493,6 @@ export class MainView extends React.Component {
                     fieldKey={"data"}
                     annotationsKey={""}
                     select={emptyFunction}
-                    chromeCollapsed={true}
                     active={returnFalse}
                     isSelected={returnFalse}
                     moveDocument={this.moveButtonDoc}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e03e4aa04..c9c6af054 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -81,7 +81,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     toggleChrome = (): void => {
         this.props.docViews.map(dv => {
             const layout = Doc.Layout(dv.Document);
-            layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : "enabled");
+            layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : StrCast(layout._replacedChrome, "enabled"));
         });
     }
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d71bad647..293a8491a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -33,7 +33,6 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     VisibleHeight?: () => number;
-    chromeCollapsed: boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
     fieldKey: string;
 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1c4818957..8dd844711 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -115,7 +115,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
                 // chrome status may also be view-mode, in reference to stacking view's toggle mode. it is essentially disabled mode, but prevents the toggle button from showing up on the left sidebar.
                 const chromeStatus = this.props.Document._chromeStatus;
-                if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
+                if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed" || chromeStatus === "replaced")) {
                     runInAction(() => this._collapsed = true);
                 }
             });
@@ -175,14 +175,14 @@ export class CollectionView extends Touchable<FieldViewProps> {
     }
 
     private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        const props = { ...this.props, ...renderProps, chromeCollapsed: this._collapsed, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" };
+        const props = { ...this.props, ...renderProps, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" };
         switch (type) {
             case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
             case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
             case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
-            case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
-            case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
-            case CollectionViewType.Multirow: return (<CollectionMultirowView chromeCollapsed={true} key="rpwview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
+            case CollectionViewType.Staff: return (<CollectionStaffView key="collview" {...props} />);
+            case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />);
+            case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />);
             case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
             case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
@@ -201,7 +201,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
-        const chrome = this.props.Document._chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) :
+        const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
             <CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />;
         return [chrome, this.SubViewHelper(type, renderProps)];
     }
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index ff7c4998f..ec05443e5 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -258,6 +258,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
         return (
             <div className={"collectionMultirowView_contents"}
                 style={{
+                    width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
+                    height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
                     marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
                     marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
                 }} ref={this.createDashEventsTarget}>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 881d80dda..09aaad9df 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1123,8 +1123,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                             CollectionView={undefined}
                             ScreenToLocalTransform={this.sidebarScreenToLocal}
                             renderDepth={this.props.renderDepth + 1}
-                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-                            chromeCollapsed={true}>
+                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
                         </CollectionFreeFormView>
                         <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
                     </div>}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 99c6fde34..db9800de4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -436,8 +436,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                 renderDepth={this.props.renderDepth + 1}
-                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-                chromeCollapsed={true}>
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
                 {this.contentFunc}
             </CollectionFreeFormView>
         </div >);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 2afdc190d..85017d3d4 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -40,6 +40,7 @@ export class PresBox extends React.Component<FieldViewProps> {
     componentDidMount() {
         const userDoc = CurrentUserUtils.UserDocument;
         this.props.Document._forceRenderEngine = "timeline";
+        this.props.Document._replacedChrome = "replaced";
         this._slideshowReaction = reaction(() => this.props.Document._viewType,
             (slideshow) => {
                 if (slideshow === CollectionViewType.Stacking || slideshow === undefined) {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d12a8d151..6695e04c3 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -354,8 +354,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                     CollectionView={undefined}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                     renderDepth={this.props.renderDepth + 1}
-                    ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-                    chromeCollapsed={true}>
+                    ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
                     {this.contentFunc}
                 </CollectionFreeFormView>
             </div>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a48dc286e..7e49d957d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -211,8 +211,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                 renderDepth={this.props.renderDepth + 1}
-                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-                chromeCollapsed={true}>
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
                 {() => [this.content]}
             </CollectionFreeFormView>
         </div >);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a7c1990e9..4f50be5b0 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -651,8 +651,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.overlayTransform}
                 renderDepth={this.props.renderDepth + 1}
-                ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}
-                chromeCollapsed={true}>
+                ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}>
             </CollectionFreeFormView>
         </div>;
     }
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 1707524cb..aac6d4e3e 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -10,22 +10,24 @@ export const documentSchema = createSchema({
     title: "string",            // document title (can be on either data document or layout)
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
     childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
-    _nativeWidth: "number",      // native width of document which determines how much document contents are scaled when the document's width is set
-    _nativeHeight: "number",     // "
-    _width: "number",            // width of document in its container's coordinate system
-    _height: "number",           // "
+    _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
+    _nativeHeight: "number",    // "
+    _width: "number",           // width of document in its container's coordinate system
+    _height: "number",          // "
     _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
-    _LODdisable: "boolean",      // whether to disbale LOD switching for CollectionFreeFormViews
+    _LODdisable: "boolean",     // whether to disbale LOD switching for CollectionFreeFormViews
     _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
+    _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
+    _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
-    creationDate: DateField,     // when the document was created
+    creationDate: DateField,    // when the document was created
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
-    onPointerDown: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
-    onPointerUp: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
+    onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
+    onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
@@ -36,7 +38,7 @@ export const documentSchema = createSchema({
     treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
     treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
     preventTreeViewOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
-    currentTimecode: "number",   // current play back time of a temporal document (video / audio)
+    currentTimecode: "number",  // current play back time of a temporal document (video / audio)
     maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
     lockedTransform: "boolean", // whether the document can be panned/zoomed
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 3a2a0f513..0185017ec 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -48,7 +48,7 @@ export class CurrentUserUtils {
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons(doc: Doc, buttons?: string[]) {
         const notes = CurrentUserUtils.setupNoteTypes(doc);
-        const emptyPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", _viewType: CollectionViewType.Stacking, _chromeStatus: "enabled", showTitle: "title", boxShadow: "0 0" });
+        const emptyPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", _viewType: CollectionViewType.Stacking, _chromeStatus: "replaced", showTitle: "title", boxShadow: "0 0" });
         const emptyCollection = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" });
         doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", _height: 75 });
         doc.activePen = doc;
@@ -270,7 +270,7 @@ export class CurrentUserUtils {
 
     // the initial presentation Doc to use
     static setupDefaultPresentation(doc: Doc) {
-        doc.curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", _viewType: CollectionViewType.Stacking, _chromeStatus: "enabled", showTitle: "title", boxShadow: "0 0" });
+        doc.curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", _viewType: CollectionViewType.Stacking, _chromeStatus: "replaced", showTitle: "title", boxShadow: "0 0" });
     }
 
     static setupMobileUploads(doc: Doc) {
-- 
cgit v1.2.3-70-g09d2


From ec5c878fb4c5f7e03fdd214c0841cf2ebf983e8c Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 11 Feb 2020 15:48:06 -0500
Subject: refactored image upload data response format, google docs working for
 specific interactions

---
 .../util/Import & Export/DirectoryImportBox.tsx    |   4 +-
 src/client/util/RichTextSchema.tsx                 |   1 +
 src/client/views/collections/CollectionSubView.tsx |   8 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 src/new_fields/RichTextUtils.ts                    |  30 ++---
 src/scraping/buxton/final/json/buxton.json         | 130 ++++++++++-----------
 src/server/ApiManagers/DownloadManager.ts          |   2 +-
 src/server/DashUploadUtils.ts                      |  37 ++++--
 src/server/apis/google/GooglePhotosUploadUtils.ts  |   3 +-
 10 files changed, 121 insertions(+), 102 deletions(-)

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

diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 071015193..d04f56e57 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -120,8 +120,8 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             runInAction(() => this.completed += batch.length);
         });
 
-        await Promise.all(uploads.map(async ({ name, type, clientAccessPath, exifData }) => {
-            const path = Utils.prepend(clientAccessPath);
+        await Promise.all(uploads.map(async ({ name, type, accessPaths, exifData }) => {
+            const path = Utils.prepend(accessPaths.agnostic.client);
             const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
             const { data, error } = exifData;
             if (document) {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f12b3632c..3cf0561dc 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -133,6 +133,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         inline: true,
         attrs: {
             src: {},
+            agnostic: { default: null },
             width: { default: 100 },
             alt: { default: null },
             title: { default: null },
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 293a8491a..20941493f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -232,8 +232,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     const split = img.split("src=\"")[1].split("\"")[0];
                     let source = split;
                     if (split.startsWith("data:image") && split.includes("base64")) {
-                        const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
-                        source = Utils.prepend(clientAccessPath);
+                        const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
+                        source = Utils.prepend(accessPaths.agnostic.client);
                     }
                     const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
                     ImageUtils.ExtractExif(doc);
@@ -312,9 +312,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     const dropFileName = file ? file.name : "-empty-";
                     promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
                         results.map(action((result: any) => {
-                            const { clientAccessPath, nativeWidth, nativeHeight, contentSize } = result;
+                            const { accessPaths, nativeWidth, nativeHeight, contentSize } = result;
                             const full = { ...options, _width: 300, title: dropFileName };
-                            const pathname = Utils.prepend(clientAccessPath);
+                            const pathname = Utils.prepend(accessPaths.agnostic.client);
                             Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
                                 if (doc) {
                                     const proto = Doc.GetProto(doc);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e4ab3e746..5c5a87cb9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -277,7 +277,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             preventDefault && e.preventDefault();
         }
-    })
+    });
 
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
         const linkDocs = DocListCast(this.props.Document.links);
@@ -636,7 +636,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         if (StrCast(tempDoc.title) === layout) {
                             foundLayout = tempDoc;
                         }
-                    })
+                    });
                 DocumentView.
                     makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout);
             } else {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index db9800de4..c0e102195 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -321,12 +321,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                     const { dataDoc } = this;
                     const { success, failure, idle, loading } = uploadIcons;
                     runInAction(() => this.uploadIcon = loading);
-                    const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
+                    const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
                     dataDoc.originalUrl = primary;
                     let succeeded = true;
                     let data: ImageField | undefined;
                     try {
-                        data = new ImageField(Utils.prepend(clientAccessPath));
+                        data = new ImageField(Utils.prepend(accessPaths.agnostic.client));
                     } catch {
                         succeeded = false;
                     }
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index 016bcc4ca..7c1fc39d8 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -114,6 +114,7 @@ export namespace RichTextUtils {
             width: number;
             title: string;
             url: string;
+            agnostic: string;
         }
 
         const parseInlineObjects = async (document: docs_v1.Schema$Document): Promise<Map<string, ImageTemplate>> => {
@@ -135,17 +136,17 @@ export namespace RichTextUtils {
 
                 for (let i = 0; i < objects.length; i++) {
                     const object = objects[i];
-                    const { clientAccessPath } = uploads[i];
+                    const { accessPaths } = uploads[i];
+                    const { agnostic, _m } = accessPaths;
                     const embeddedObject = object.inlineObjectProperties!.embeddedObject!;
                     const size = embeddedObject.size!;
                     const width = size.width!.magnitude!;
-                    const ext = extname(clientAccessPath);
-                    const url = Utils.prepend(clientAccessPath.replace(ext, "_m" + ext));
 
                     inlineObjectMap.set(object.objectId!, {
                         title: embeddedObject.title || `Imported Image from ${document.title}`,
                         width,
-                        url
+                        url: Utils.prepend(_m.client),
+                        agnostic: Utils.prepend(agnostic.client)
                     });
                 }
             }
@@ -267,19 +268,19 @@ export namespace RichTextUtils {
         };
 
         const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => {
-            const { url: src, width } = image;
+            const { url: src, width, agnostic } = image;
             let docid: string;
-            const guid = Utils.GenerateDeterministicGuid(src);
+            const guid = Utils.GenerateDeterministicGuid(agnostic);
             const backingDocId = StrCast(textNote[guid]);
             if (!backingDocId) {
-                const backingDoc = Docs.Create.ImageDocument(src, { _width: 300, _height: 300 });
+                const backingDoc = Docs.Create.ImageDocument(agnostic, { _width: 300, _height: 300 });
                 DocumentView.makeCustomViewClicked(backingDoc, undefined, Docs.Create.FreeformDocument);
                 docid = backingDoc[Id];
                 textNote[guid] = docid;
             } else {
                 docid = backingDocId;
             }
-            return schema.node("image", { src, width, docid, float: null, location: "onRight" });
+            return schema.node("image", { src, agnostic, width, docid, float: null, location: "onRight" });
         };
 
         const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
@@ -435,7 +436,7 @@ export namespace RichTextUtils {
                     const width = attrs.width;
                     requests.push(await EncodeImage({
                         startIndex: position + nodeSize - 1,
-                        uri: attrs.src,
+                        uri: attrs.agnostic,
                         width: Number(typeof width === "string" ? width.replace("px", "") : width)
                     }));
                 }
@@ -498,15 +499,18 @@ export namespace RichTextUtils {
             };
         };
 
-        const EncodeImage = async (information: ImageInformation) => {
-            const source = [Docs.Create.ImageDocument(information.uri)];
+        const EncodeImage = async ({ uri, width, startIndex }: ImageInformation) => {
+            if (!uri) {
+                return {};
+            }
+            const source = [Docs.Create.ImageDocument(uri)];
             const baseUrls = await GooglePhotos.Transactions.UploadThenFetch(source);
             if (baseUrls) {
                 return {
                     insertInlineImage: {
                         uri: baseUrls[0],
-                        objectSize: { width: { magnitude: information.width, unit: "PT" } },
-                        location: { index: information.startIndex }
+                        objectSize: { width: { magnitude: width, unit: "PT" } },
+                        location: { index: startIndex }
                     }
                 };
             }
diff --git a/src/scraping/buxton/final/json/buxton.json b/src/scraping/buxton/final/json/buxton.json
index 5c2e2c90c..16b28916b 100644
--- a/src/scraping/buxton/final/json/buxton.json
+++ b/src/scraping/buxton/final/json/buxton.json
@@ -18,9 +18,9 @@
             "3DCad_Brochure.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_19ce4ebd-f207-4724-b8bf-610c8e1cf322.png",
-            "/files/images/buxton/upload_4dde8de0-f1ec-4ffb-b7cc-57b8c4ae6c94.png",
-            "/files/images/buxton/upload_825e8ed8-063c-428b-917d-eb62ea218f05.png"
+            "/files/images/buxton/upload_793981b0-d7da-45d1-a643-596cc3834166.png",
+            "/files/images/buxton/upload_6259e28a-055e-4b02-b547-e72c5e6fa3c0.png",
+            "/files/images/buxton/upload_4f4fb80f-467a-41e7-9bf5-1b1f377b2ec4.png"
         ],
         "title": "3Dconnexion CadMan 3D Motion Controller",
         "company": "3Dconnexion",
@@ -63,11 +63,11 @@
             "SpaceMouse_Plus_Info_Sheet.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_f84cb54e-9bdb-4fbc-9503-6cfa8ddd65be.jpg",
-            "/files/images/buxton/upload_506b5361-a9fd-4440-a19b-5b4af461878a.png",
-            "/files/images/buxton/upload_60380383-3603-4624-98cf-50a0d4afe4c9.png",
-            "/files/images/buxton/upload_2fad6fb2-f1e1-4fe3-915b-33586f05fcad.png",
-            "/files/images/buxton/upload_effcd815-49eb-4cd4-82cf-2519e58eb729.png"
+            "/files/images/buxton/upload_4a6cbfa8-964b-41d3-9f24-7d1aed22cd79.jpg",
+            "/files/images/buxton/upload_8c33d572-07da-4599-944c-eb2370b16a63.png",
+            "/files/images/buxton/upload_9afc5cca-6208-4944-a0f6-1c3fcd41fdcf.png",
+            "/files/images/buxton/upload_74e79798-9c9e-4c54-b42f-5813f218bc63.png",
+            "/files/images/buxton/upload_2bea6876-dc4e-4681-a1d3-433caec163c1.png"
         ],
         "title": "3Dconnexion Magellan/SpaceMouse Plus",
         "company": "3Dconnexion",
@@ -114,13 +114,13 @@
             "SpaceBall_5000_Data_Sheet.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_81972990-8cad-4299-926a-fe5d49711a80.jpg",
-            "/files/images/buxton/upload_04103620-72e4-431f-b34f-61d0f158277a.png",
-            "/files/images/buxton/upload_9bfe7c5a-1c5e-46e3-8f8d-4e0eec3e38e3.png",
-            "/files/images/buxton/upload_77da85f5-6575-4a8d-acb6-52f2df3307b0.png",
-            "/files/images/buxton/upload_1c041a33-b258-42d1-b9f2-d610a4838fb3.jpg",
-            "/files/images/buxton/upload_841f0d98-27d7-4298-a71d-c5cba72a262a.jpg",
-            "/files/images/buxton/upload_0e0eaf99-f8bb-4f02-ad46-651cd67c13b1.jpg"
+            "/files/images/buxton/upload_94e39580-5cca-46e9-ae49-ab6cd2fe3cad.jpg",
+            "/files/images/buxton/upload_eacee8a5-9849-4401-9c38-aa46fc0f517b.png",
+            "/files/images/buxton/upload_9b0b1f35-ac7c-4160-8806-8d243223872b.png",
+            "/files/images/buxton/upload_cf0c6e39-9ae6-48ce-90bd-d9f335503439.png",
+            "/files/images/buxton/upload_b4285b9f-26cd-4de5-8e37-c4e563c3e7f6.jpg",
+            "/files/images/buxton/upload_5b6ff31b-81b2-4a74-8591-27730c24225b.jpg",
+            "/files/images/buxton/upload_64dcea06-3b35-4979-bf40-5dfa5d4668b1.jpg"
         ],
         "title": "3Dconnexion Spaceball 5000",
         "company": "3Dconnexion",
@@ -171,13 +171,13 @@
             "SpaceNavigator_Press_Release.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_b44ec511-334e-46c0-a315-63d90fac8117.jpg",
-            "/files/images/buxton/upload_734ae360-faaf-4c78-89b4-a3f120fe233e.png",
-            "/files/images/buxton/upload_d20bbf6a-7310-407a-8b85-c607e9cf7f73.png",
-            "/files/images/buxton/upload_e9236600-dd5f-4805-92ca-805d5a301aa1.jpg",
-            "/files/images/buxton/upload_c85c977d-ef99-44be-b55c-8e8dde124e83.png",
-            "/files/images/buxton/upload_a6034d66-7de6-4d4b-bd64-ea95cc00ae8a.png",
-            "/files/images/buxton/upload_07415a65-964a-4936-aa6e-79861e8ebcbf.png"
+            "/files/images/buxton/upload_73511beb-f2bf-4f28-a1ea-64ef52a00426.jpg",
+            "/files/images/buxton/upload_4e088fb4-e5ff-4a1b-ba88-5f4575c86fe3.png",
+            "/files/images/buxton/upload_876a9921-3eb1-4c35-99e3-b82924fb88c4.png",
+            "/files/images/buxton/upload_d23c2886-c2b9-4558-9bfb-966aca7be20e.jpg",
+            "/files/images/buxton/upload_aef9171b-2e19-4fcb-b2dc-2281e89d498b.png",
+            "/files/images/buxton/upload_bbdaf252-1689-4680-8b19-ec5e79088e44.png",
+            "/files/images/buxton/upload_4cf49440-cf6b-484d-8391-3a2b0fae2b7e.png"
         ],
         "title": "3Dconnexion SpaceNavigator ",
         "company": "3Dconnexion",
@@ -222,12 +222,12 @@
             "3M_2006_Catalogue_p18.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_52b83c2a-5fe4-42aa-a488-5667b0a5beae.jpg",
-            "/files/images/buxton/upload_e05d8710-d82b-4f3b-8940-3745db1c855c.jpg",
-            "/files/images/buxton/upload_557c6fc7-04a3-4698-acde-4e53ceb6793c.jpg",
-            "/files/images/buxton/upload_d9723beb-bfe3-4556-b17d-aedf2db59777.jpg",
-            "/files/images/buxton/upload_5cb15815-3cab-406f-bf95-f9410997b4f4.jpg",
-            "/files/images/buxton/upload_a7fc91e0-7672-4f81-a8f3-66072997bd44.jpg"
+            "/files/images/buxton/upload_02df5121-8eff-452c-a52b-a44238afc06d.jpg",
+            "/files/images/buxton/upload_169aa4ee-4c34-43a2-8302-48bb3b7a01b6.jpg",
+            "/files/images/buxton/upload_a4d11c27-e4fa-4912-96da-3d971ec49ca0.jpg",
+            "/files/images/buxton/upload_7e9e1862-72e7-4f0d-8451-36de8c1bbe9c.jpg",
+            "/files/images/buxton/upload_76fa705f-2470-439c-acf6-03482d7a9570.jpg",
+            "/files/images/buxton/upload_7b6dd2c9-b88f-4958-a4d0-94bb42172e26.jpg"
         ],
         "title": "3M EM500 Ergonomic Mouse",
         "company": "3M",
@@ -268,13 +268,13 @@
             "Abaton_ProPoint_Brochure.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_f6f038a9-3583-4d21-9a43-f762e1df0b52.jpg",
-            "/files/images/buxton/upload_ad5f2bbd-66be-4dde-afe4-60e353a63f74.jpg",
-            "/files/images/buxton/upload_2e384260-5d2b-47a4-9508-028462a5a1e7.jpg",
-            "/files/images/buxton/upload_4dfcd3af-f06d-4c3e-8dff-7c43acd79a16.jpg",
-            "/files/images/buxton/upload_19eea9af-abec-4617-8b8d-fcb6637420de.jpg",
-            "/files/images/buxton/upload_89702b8a-fcb5-426d-b5af-c58e62d1c8ff.jpg",
-            "/files/images/buxton/upload_44419996-994e-471c-8dc7-cda22b00ac24.jpg"
+            "/files/images/buxton/upload_37169968-d329-40c5-9483-6bc198edc43c.jpg",
+            "/files/images/buxton/upload_64731ea4-a3aa-43ec-a56d-d4f6ecba7cf0.jpg",
+            "/files/images/buxton/upload_c41feac8-3b98-4d85-b9c7-79b3482a0695.jpg",
+            "/files/images/buxton/upload_5f67807b-f61b-4816-b93f-6d88c95b2418.jpg",
+            "/files/images/buxton/upload_a3e3cab8-48ab-43c2-bed9-5cc0ce68928d.jpg",
+            "/files/images/buxton/upload_4c3ac379-6703-4c87-a4a0-ca450a1151d2.jpg",
+            "/files/images/buxton/upload_8df14dc0-d75d-41f0-9e70-a221598d3df3.jpg"
         ],
         "title": "Abaton ProPoint Optical Trackball",
         "company": "Abaton",
@@ -305,8 +305,8 @@
             "Active_Book_Brochure_p1.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_2159da7b-9ebd-41ed-8b60-9eb36a6c6685.jpg",
-            "/files/images/buxton/upload_6d470914-fc41-4151-858b-f945807e62a2.png"
+            "/files/images/buxton/upload_9c8aa6a2-570f-4915-b840-504c8531c341.jpg",
+            "/files/images/buxton/upload_fbb7ad12-2a5d-4f76-89a9-ec30d7260031.png"
         ],
         "title": "Active Book Company Active Book Prototype",
         "company": "Active Book Company",
@@ -342,11 +342,11 @@
             "Adesso_ACK-540PW_June 21_2003.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_189469a3-1133-4a4f-8720-19c04f29684e.jpg",
-            "/files/images/buxton/upload_73f5d066-f9b5-41ce-9ce3-e27253ef51e7.jpg",
-            "/files/images/buxton/upload_498e5cbd-d65e-4b01-bed2-bcb2723ffee5.jpg",
-            "/files/images/buxton/upload_97ee9d03-07e0-448c-89f6-6ac61948de68.jpg",
-            "/files/images/buxton/upload_661e7387-dcd7-4ca1-8493-2e94f8803ef7.jpg"
+            "/files/images/buxton/upload_967656ff-44d7-4693-bcb0-dc0eab204413.jpg",
+            "/files/images/buxton/upload_9ffd4ccb-8ca5-4926-8fa2-38277b2ccfb6.jpg",
+            "/files/images/buxton/upload_22399981-f8c0-46e0-953c-89f7eb043ad2.jpg",
+            "/files/images/buxton/upload_15ad02a9-123f-4e7e-94c5-d959076520f0.jpg",
+            "/files/images/buxton/upload_8905097a-3568-4d37-9ad3-126a0e429f12.jpg"
         ],
         "title": "Adesso ACK-540PB PS/2 Mini PS/2 Touchpad Keyboard",
         "company": "Adesso",
@@ -405,17 +405,17 @@
             "Adesso_KP_Mouse_11_Product.JPG"
         ],
         "__images": [
-            "/files/images/buxton/upload_0a127e30-9014-4300-bade-07945fba940f.jpg",
-            "/files/images/buxton/upload_ca20611c-9061-4e56-9d72-4c4a3c798d3f.jpg",
-            "/files/images/buxton/upload_12b46021-eeef-47f0-9e55-ca9e9b76f9e4.jpg",
-            "/files/images/buxton/upload_5e86ad07-77af-4d3e-a82e-eaa9cafdbbe4.jpg",
-            "/files/images/buxton/upload_4be64833-53e9-4216-b7ec-d521e0b87933.jpg",
-            "/files/images/buxton/upload_7cb731b5-5c52-47e0-a33b-c113530478f2.jpg",
-            "/files/images/buxton/upload_b20407b3-3578-4fa5-8f16-e0bf729e4daa.jpg",
-            "/files/images/buxton/upload_bee2fcb8-702a-46aa-9ec3-eb9b50ef1ba7.jpg",
-            "/files/images/buxton/upload_0ee0f6d5-34fc-41f3-9c8e-cf462dbcc40b.jpg",
-            "/files/images/buxton/upload_8f442582-b901-4a38-8df6-5a9b3195df9f.jpg",
-            "/files/images/buxton/upload_3f96763c-c3af-46a7-8831-3206c8bc460e.jpg"
+            "/files/images/buxton/upload_5bc99192-0935-4f6d-8d6c-a2218d55b359.jpg",
+            "/files/images/buxton/upload_52a6212e-c79d-4ec6-86a0-a3ca4ea54387.jpg",
+            "/files/images/buxton/upload_997acb64-0e08-4af2-b0e3-0b86a18bace5.jpg",
+            "/files/images/buxton/upload_00586980-0a08-43e1-9721-c2f5dcd1079e.jpg",
+            "/files/images/buxton/upload_3f695b11-dc1d-4d54-abec-eacfac80bdc7.jpg",
+            "/files/images/buxton/upload_161f1420-9594-4dca-ba62-1ca32e9a2028.jpg",
+            "/files/images/buxton/upload_a8b4ea6e-f1f8-47db-a796-72f566e60e6f.jpg",
+            "/files/images/buxton/upload_b6365e4e-4656-4bbf-aec6-feb920be6df9.jpg",
+            "/files/images/buxton/upload_1a0e9061-39e1-4dec-8add-5cc1abd110b5.jpg",
+            "/files/images/buxton/upload_d9bba544-f2b1-4a15-81e5-0d10e60d7881.jpg",
+            "/files/images/buxton/upload_56d486df-d7bb-44b0-80ad-c41c63590bae.jpg"
         ],
         "title": "Adesso 2-in-1 Optical Keypad Calculator Mouse AKP-170",
         "company": "Adesso Inc",
@@ -477,18 +477,18 @@
             "NB75D_Mouse_Manual.jpg"
         ],
         "__images": [
-            "/files/images/buxton/upload_dae8f950-38bf-4fdf-8db0-153e3cae9cae.jpg",
-            "/files/images/buxton/upload_3e054d83-d01f-4d3d-aad2-fa8ee1d22a97.jpg",
-            "/files/images/buxton/upload_e9c2ddb0-0684-4bb4-a88c-5ee8074e456e.jpg",
-            "/files/images/buxton/upload_9c829a69-7d45-4d25-b906-98153aa03001.jpg",
-            "/files/images/buxton/upload_4faa425c-a100-4ce4-869c-a6c6705779ee.jpg",
-            "/files/images/buxton/upload_ed20ac31-69f6-45b8-b8e0-b8ad829ba002.jpg",
-            "/files/images/buxton/upload_f3d3d8f5-01cf-4cd1-9d1c-0a11a8c3e239.jpg",
-            "/files/images/buxton/upload_af1fa3b6-4537-416a-b626-34bed1154f35.jpg",
-            "/files/images/buxton/upload_5127f377-b697-4ad7-9fb3-fd6f82017c3c.jpg",
-            "/files/images/buxton/upload_061ae6a2-e083-492a-bc8a-826c4be00790.jpg",
-            "/files/images/buxton/upload_32e91966-7f1b-4cd7-858b-9cdb63affd13.jpg",
-            "/files/images/buxton/upload_ccc9de21-b047-4f9c-89c6-08fb3d206c7a.jpg"
+            "/files/images/buxton/upload_2db5c520-b370-4f03-8481-908e42428466.jpg",
+            "/files/images/buxton/upload_b28643eb-57a0-4602-b7cc-c41d553628b9.jpg",
+            "/files/images/buxton/upload_2478a89a-4eea-4e4c-af83-ecd292f44f3d.jpg",
+            "/files/images/buxton/upload_113d7356-f550-4491-849d-f69087c73918.jpg",
+            "/files/images/buxton/upload_44c40c44-ade6-499d-951c-4b436131e66e.jpg",
+            "/files/images/buxton/upload_d91f9756-09bc-447f-8f04-dc78344a5f11.jpg",
+            "/files/images/buxton/upload_b8c51011-5f93-43f3-85aa-b611a3b3cf93.jpg",
+            "/files/images/buxton/upload_483544a3-c3f7-4c8f-a236-def10d32f4f0.jpg",
+            "/files/images/buxton/upload_ea048294-3f57-42c3-b5b4-27062a5755dc.jpg",
+            "/files/images/buxton/upload_8eb1b9b1-9ca3-49c8-9bd4-7302a9c1f07e.jpg",
+            "/files/images/buxton/upload_029aa1d6-056d-4cf0-8eaf-b23b3d0e4317.jpg",
+            "/files/images/buxton/upload_3acc0955-da14-4a53-ac58-2e494e04ca78.jpg"
         ],
         "title": "A4 Tech BatteryFREE Wireless Optical Mouse Model NB-75D",
         "company": "A4Tech",
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index fad5e6789..01d2dfcad 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -254,7 +254,7 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera
                 // and dropped in the browser and thus hosted remotely) so we upload it
                 // to our server and point the zip file to it, so it can bundle up the bytes
                 const information = await DashUploadUtils.UploadImage(result);
-                path = information instanceof Error ? "" : information.serverAccessPaths[SizeSuffix.Original];
+                path = information instanceof Error ? "" : information.accessPaths[SizeSuffix.Original].server;
             }
             // write the file specified by the path to the directory in the
             // zip file given by the prefix.
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 83a0064e8..0f1758c26 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -61,13 +61,16 @@ export namespace DashUploadUtils {
     const type = "content-type";
 
     export interface ImageUploadInformation {
-        clientAccessPath: string;
-        serverAccessPaths: { [key: string]: string };
+        accessPaths: AccessPathInfo;
         exifData: EnrichedExifData;
         contentSize?: number;
         contentType?: string;
     }
 
+    export interface AccessPathInfo {
+        [suffix: string]: { client: string, server: string };
+    }
+
     const { imageFormats, videoFormats, applicationFormats } = AcceptibleMedia;
 
     export async function upload(file: File): Promise<any> {
@@ -94,7 +97,7 @@ export namespace DashUploadUtils {
         }
 
         console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`));
-        return { clientAccessPath: undefined };
+        return { accessPaths: undefined };
     }
 
     async function UploadPdf(absolutePath: string) {
@@ -213,29 +216,40 @@ export namespace DashUploadUtils {
         };
     };
 
-    export async function MoveParsedFile(absolutePath: string, destination: Directory): Promise<{ clientAccessPath: Opt<string> }> {
-        return new Promise<{ clientAccessPath: Opt<string> }>(resolve => {
+    export async function MoveParsedFile(absolutePath: string, destination: Directory): Promise<Opt<AccessPathInfo>> {
+        return new Promise<Opt<AccessPathInfo>>(resolve => {
             const filename = basename(absolutePath);
             const destinationPath = serverPathToFile(destination, filename);
             rename(absolutePath, destinationPath, error => {
-                resolve({ clientAccessPath: error ? undefined : clientPathToFile(destination, filename) });
+                resolve(error ? undefined : {
+                    agnostic: getAccessPaths(destination, filename)
+                });
             });
         });
     }
 
+    function getAccessPaths(directory: Directory, fileName: string) {
+        return {
+            client: clientPathToFile(directory, fileName),
+            server: serverPathToFile(directory, fileName)
+        };
+    }
+
     export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise<ImageUploadInformation> => {
         const { requestable, source, ...remaining } = metadata;
         const extension = `.${remaining.contentType.split("/")[1].toLowerCase()}`;
         const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}${extension}`;
+        const { images } = Directory;
         const information: ImageUploadInformation = {
-            clientAccessPath: clientPathToFile(Directory.images, resolved),
-            serverAccessPaths: {},
+            accessPaths: {
+                agnostic: getAccessPaths(images, resolved)
+            },
             ...remaining
         };
         const outputPath = pathToDirectory(Directory.images);
         const writtenFiles = await outputResizedImages(() => request(requestable), outputPath, resolved, extension);
         for (const suffix of Object.keys(writtenFiles)) {
-            information.serverAccessPaths[suffix] = serverPathToFile(Directory.images, writtenFiles[suffix]);
+            information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]);
         }
         if (isLocal().test(source) && cleanUp) {
             unlinkSync(source);
@@ -270,10 +284,9 @@ export namespace DashUploadUtils {
     export async function outputResizedImages(readStreamSource: () => ReadStreamLike | Promise<ReadStreamLike>, outputPath: string, fileName: string, ext: string) {
         const writtenFiles: { [suffix: string]: string } = {};
         for (const { resizer, suffix } of resizers(ext)) {
-            const resizedPath = path.resolve(outputPath, InjectSize(fileName, suffix));
-            writtenFiles[suffix] = resizedPath;
+            const resolved = writtenFiles[suffix] = InjectSize(fileName, suffix);
             await new Promise<void>(async (resolve, reject) => {
-                const writeStream = createWriteStream(resizedPath);
+                const writeStream = createWriteStream(path.resolve(outputPath, resolved));
                 let readStream: ReadStreamLike;
                 const source = readStreamSource();
                 if (source instanceof Promise) {
diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts
index 8ae63caa3..d305eed0a 100644
--- a/src/server/apis/google/GooglePhotosUploadUtils.ts
+++ b/src/server/apis/google/GooglePhotosUploadUtils.ts
@@ -84,6 +84,7 @@ export namespace GooglePhotosUploadUtils {
         if (!DashUploadUtils.validateExtension(url)) {
             return undefined;
         }
+        const body = await request(url, { encoding: null }); // returns a readable stream with the unencoded binary image data
         const parameters = {
             method: 'POST',
             uri: prepend('uploads'),
@@ -92,7 +93,7 @@ export namespace GooglePhotosUploadUtils {
                 'X-Goog-Upload-File-Name': filename || path.basename(url),
                 'X-Goog-Upload-Protocol': 'raw'
             },
-            body: await request(url, { encoding: null }) // returns a readable stream with the unencoded binary image data
+            body
         };
         return new Promise((resolve, reject) => request(parameters, (error, _response, body) => {
             if (error) {
-- 
cgit v1.2.3-70-g09d2


From bea48a0fcb2296457804e3136642246db8298634 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 11 Feb 2020 19:10:51 -0500
Subject: fixed issues with adding to a template layout instance

---
 src/client/views/collections/CollectionSubView.tsx | 5 ++++-
 src/client/views/collections/CollectionView.tsx    | 9 ++++++---
 2 files changed, 10 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 20941493f..32480ad4e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -85,7 +85,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this._childLayoutDisposer && this._childLayoutDisposer();
         }
 
-        @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
+        @computed get dataDoc() {
+            return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
+                this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+        }
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
         // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8dd844711..c17d83c36 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -37,6 +37,7 @@ import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
+import { List } from '../../../new_fields/List';
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -132,8 +133,9 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = this.props.Document[DataSym];
-        Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
+        const targetDataDoc = this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document[DataSym]);
+        targetDataDoc[this.props.fieldKey] = new List<Doc>([...DocListCast(targetDataDoc[this.props.fieldKey]), doc]);  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
+        // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
         return true;
@@ -143,7 +145,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
     removeDocument(doc: Doc): boolean {
         const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
-        const value = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
+        const targetDataDoc = this.props.Document.resolvedDataDoc ? this.props.Document : this.props.Document[DataSym];
+        const value = Cast(targetDataDoc[this.props.fieldKey], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
-- 
cgit v1.2.3-70-g09d2


From 2131c829b8632a87b8ba52bc311d3b0346eed3f7 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 11 Feb 2020 21:36:20 -0500
Subject: did the maths :')

---
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 74 +++++++++++++++-------
 3 files changed, 53 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8679c8bd1..12906ee83 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -51,11 +51,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         private _childLayoutDisposer?: IReactionDisposer;
+        protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
+                this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index b70697e9a..2213b7882 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -101,7 +101,7 @@
 }
 
 .pullpane-indicator {
-    z-index: 999;
+    z-index: 99999;
     background-color: rgba($color: #000000, $alpha: .4);
     position: absolute;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8f555f315..bf517e7c2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload, faTextHeight } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
@@ -47,6 +47,7 @@ import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { MainView } from "../../MainView";
+import { TouchScrollableMenuItem } from "../../TouchScrollableMenu";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -627,12 +628,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         // use the centerx and centery as the "new mouse position"
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                        // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
 
                         if (!this._pullDirection) { // if we are not bezel movement
                             this.pan({ clientX: centerX, clientY: centerY });
                         } else {
                             this._pullCoords = [centerX, centerY];
-                            console.log(MainView.Instance.flyoutWidth);
                         }
 
                         this._lastX = centerX;
@@ -657,22 +658,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (pt1 && pt2) {
                 const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                // const screenPoint = this.getTransform().inverse().transformPoint(centerX, centerY);
                 this._lastX = centerX;
                 this._lastY = centerY;
+                const screenBox = this._mainCont?.getBoundingClientRect();
 
+
+                // console.log(this.props.PanelWidth(), transformed[0]);
                 // determine if we are using a bezel movement
-                if ((this.props.PanelWidth() - this._lastX) < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "right";
-                } else if (this._lastX < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "left";
-                } else if (this.props.PanelHeight() - this._lastY < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "bottom";
-                } else if (this._lastY < 120) { // to account for header
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "top";
+                if (screenBox) {
+                    if ((screenBox.right - centerX) < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "right";
+                    } else if (centerX - screenBox.left < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "left";
+                    } else if (screenBox.bottom - centerY < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "bottom";
+                    } else if (centerY - screenBox.top < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "top";
+                    }
                 }
 
 
@@ -708,6 +715,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             default:
                 break;
         }
+        console.log("");
 
         this._pullDirection = "";
         this._pullCoords = [0, 0];
@@ -1120,6 +1128,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     render() {
         TraceMobx();
+        const clientRect = this._mainCont?.getBoundingClientRect();
+        // console.log('clientrect has upd8ed', clientRect, this._pullCoords);
+        console.log(
+            'left', clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - MainView.Instance.flyoutWidth : clientRect.x - MainView.Instance.flyoutWidth : "auto",
+            'top indicator', clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : clientRect.y - 20 : "auto",
+            'width', clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+            'height', clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0);
+        console.log(clientRect);
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         // this.Document.fitX = this.contentBounds && this.contentBounds.x;
         // this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1146,16 +1162,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
-                    // width: this._pullDirection === "left" || this._pullDirection === "right" ? Math.abs(this.props.PanelWidth() - this._pullCoords[0]) : this.props.PanelWidth(),
-                    // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
-                    // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
-                    // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
-                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? MainView.Instance.getPWidth() - this._pullCoords[0] + MainView.Instance.flyoutWidth : MainView.Instance.getPWidth(),
-                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? MainView.Instance.getPHeight() - this._pullCoords[1] : MainView.Instance.getPHeight(),
-                    left: this._pullDirection === "right" ? undefined : 0,
-                    right: this._pullDirection === "right" ? 0 : undefined,
-                    top: this._pullDirection === "bottom" ? undefined : 0,
-                    bottom: this._pullDirection === "bottom" ? 0 : undefined
+                    // width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    // height: clientRect ? this._pullDirection === "top" ? this._pullCoords[0] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[0] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "right" ? undefined : clientRect.left : 0,
+                    // right: clientRect ? this._pullDirection === "left" ? undefined : clientRect.right : 0,
+                    // top: clientRect ? this._pullDirection === "bottom" ? undefined : clientRect.top : 0,
+                    // bottom: clientRect ? this._pullDirection === "top" ? undefined : clientRect.bottom : 0,
+
+
+
+                    // hahahahahahahhahahahaa
+
+                    width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" || this._pullDirection === "bottom" ? clientRect.left - MainView.Instance.flyoutWidth : this._pullCoords[0] - clientRect.left : undefined,
+                    // top: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" ? clientRect.top - 20 : this._pullCoords[1] - clientRect.top : undefined,
+
+                    // left: x-axis, top: y-axis
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+
                 }}>
             </div>
 
-- 
cgit v1.2.3-70-g09d2


From 1ae7bb0792c233d8379e1949f965c864667cf3ae Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 12 Feb 2020 13:13:07 -0500
Subject: fixed upload of videos.

---
 src/client/views/collections/CollectionSubView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32480ad4e..acab45078 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -315,9 +315,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     const dropFileName = file ? file.name : "-empty-";
                     promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
                         results.map(action((result: any) => {
-                            const { accessPaths, nativeWidth, nativeHeight, contentSize } = result;
+                            const { agnostic, accessPaths, nativeWidth, nativeHeight, contentSize } = result;
                             const full = { ...options, _width: 300, title: dropFileName };
-                            const pathname = Utils.prepend(accessPaths.agnostic.client);
+                            const pathname = Utils.prepend(accessPaths?.agnostic.client || agnostic.client);
                             Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
                                 if (doc) {
                                     const proto = Doc.GetProto(doc);
-- 
cgit v1.2.3-70-g09d2


From 736dde96336c5a00d3761f0894157b51a26081cc Mon Sep 17 00:00:00 2001
From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com>
Date: Wed, 12 Feb 2020 16:32:48 -0500
Subject: standardized upload file response, particularly as it applies to
 video

---
 src/client/views/collections/CollectionSubView.tsx |  4 ++--
 src/server/DashUploadUtils.ts                      | 10 ++++++----
 2 files changed, 8 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index acab45078..32480ad4e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -315,9 +315,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     const dropFileName = file ? file.name : "-empty-";
                     promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
                         results.map(action((result: any) => {
-                            const { agnostic, accessPaths, nativeWidth, nativeHeight, contentSize } = result;
+                            const { accessPaths, nativeWidth, nativeHeight, contentSize } = result;
                             const full = { ...options, _width: 300, title: dropFileName };
-                            const pathname = Utils.prepend(accessPaths?.agnostic.client || agnostic.client);
+                            const pathname = Utils.prepend(accessPaths.agnostic.client);
                             Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
                                 if (doc) {
                                     const proto = Doc.GetProto(doc);
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 0f1758c26..9ccc860f1 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -97,7 +97,7 @@ export namespace DashUploadUtils {
         }
 
         console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`));
-        return { accessPaths: undefined };
+        return { accessPaths: {} };
     }
 
     async function UploadPdf(absolutePath: string) {
@@ -216,13 +216,15 @@ export namespace DashUploadUtils {
         };
     };
 
-    export async function MoveParsedFile(absolutePath: string, destination: Directory): Promise<Opt<AccessPathInfo>> {
-        return new Promise<Opt<AccessPathInfo>>(resolve => {
+    export async function MoveParsedFile(absolutePath: string, destination: Directory): Promise<Opt<{ accessPaths: AccessPathInfo }>> {
+        return new Promise(resolve => {
             const filename = basename(absolutePath);
             const destinationPath = serverPathToFile(destination, filename);
             rename(absolutePath, destinationPath, error => {
                 resolve(error ? undefined : {
-                    agnostic: getAccessPaths(destination, filename)
+                    accessPaths: {
+                        agnostic: getAccessPaths(destination, filename)
+                    }
                 });
             });
         });
-- 
cgit v1.2.3-70-g09d2


From 28079636e5b58f587ec42cbb49f629165181296c Mon Sep 17 00:00:00 2001
From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com>
Date: Wed, 12 Feb 2020 18:09:16 -0500
Subject: more informative collection sub view upload alerts

---
 src/client/views/collections/CollectionSubView.tsx | 109 ++++++++++++---------
 1 file changed, 60 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32480ad4e..343be1b58 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -283,63 +283,74 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 const albumId = matches[3];
                 const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
                 console.log(mediaItems);
+                return;
             }
-            const batch = UndoManager.StartBatch("collection view drop");
-            const promises: Promise<void>[] = [];
-            // tslint:disable-next-line:prefer-for-of
-            for (let i = 0; i < e.dataTransfer.items.length; i++) {
-                const item = e.dataTransfer.items[i];
-                if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
-                    let str: string;
-                    const prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
-                        .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
-                        .then(result => {
-                            const type = result["content-type"];
-                            if (type) {
-                                Docs.Get.DocumentFromType(type, str, options)
-                                    .then(doc => doc && this.props.addDocument(doc));
-                            }
-                        });
-                    promises.push(prom);
-                }
-                const type = item.type;
-                if (item.kind === "file") {
-                    const file = item.getAsFile();
-                    const formData = new FormData();
-
-                    if (!file || !file.type) {
-                        continue;
+            const { items } = e.dataTransfer;
+            const { length } = items;
+            if (length) {
+                const batch = UndoManager.StartBatch("collection view drop");
+                const promises: Promise<void>[] = [];
+                // tslint:disable-next-line:prefer-for-of
+                for (let i = 0; i < length; i++) {
+                    const item = e.dataTransfer.items[i];
+                    if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
+                        let str: string;
+                        const prom = new Promise<string>(resolve => item.getAsString(resolve))
+                            .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
+                            .then(result => {
+                                const type = result["content-type"];
+                                if (type) {
+                                    Docs.Get.DocumentFromType(type, str, options)
+                                        .then(doc => doc && this.props.addDocument(doc));
+                                }
+                            });
+                        promises.push(prom);
                     }
+                    const type = item.type;
+                    if (item.kind === "file") {
+                        const file = item.getAsFile();
+                        const formData = new FormData();
+
+                        if (!file || !file.type) {
+                            continue;
+                        }
 
-                    formData.append('file', file);
-                    const dropFileName = file ? file.name : "-empty-";
-                    promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
-                        results.map(action((result: any) => {
-                            const { accessPaths, nativeWidth, nativeHeight, contentSize } = result;
-                            const full = { ...options, _width: 300, title: dropFileName };
-                            const pathname = Utils.prepend(accessPaths.agnostic.client);
-                            Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
-                                if (doc) {
-                                    const proto = Doc.GetProto(doc);
-                                    proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
-                                    nativeWidth && (proto["data-nativeWidth"] = nativeWidth);
-                                    nativeHeight && (proto["data-nativeHeight"] = nativeHeight);
-                                    contentSize && (proto.contentSize = contentSize);
-                                    this.props?.addDocument(doc);
+                        formData.append('file', file);
+                        const dropFileName = file ? file.name : "-empty-";
+                        promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
+                            results.map(action((result: any) => {
+                                const { accessPaths, nativeWidth, nativeHeight, contentSize } = result;
+                                if (Object.keys(accessPaths).length) {
+                                    const full = { ...options, _width: 300, title: dropFileName };
+                                    const pathname = Utils.prepend(accessPaths.agnostic.client);
+                                    Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
+                                        if (doc) {
+                                            const proto = Doc.GetProto(doc);
+                                            proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+                                            nativeWidth && (proto["data-nativeWidth"] = nativeWidth);
+                                            nativeHeight && (proto["data-nativeHeight"] = nativeHeight);
+                                            contentSize && (proto.contentSize = contentSize);
+                                            this.props?.addDocument(doc);
+                                        }
+                                    });
+                                } else {
+                                    alert("Upload failed...");
                                 }
-                            });
+                            }));
                         }));
-                    }));
+                    }
                 }
-            }
 
-            if (promises.length) {
-                Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
-            } else {
-                if (text && !text.includes("https://")) {
-                    this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
+                if (promises.length) {
+                    Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
+                } else {
+                    if (text && !text.includes("https://")) {
+                        this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
+                    }
+                    batch.end();
                 }
-                batch.end();
+            } else {
+                alert("No uploadable content found.");
             }
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 553fb2a11aa638c35192a1a2f7da99729867e542 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 13 Feb 2020 22:30:32 -0500
Subject: starting a broad cleanup of code.  trying to make standard doc field
 names be more regular

---
 src/client/documents/Documents.ts                  | 15 ++--
 src/client/util/DragManager.ts                     |  1 -
 src/client/util/RichTextRules.ts                   |  3 +-
 src/client/views/MainView.tsx                      |  7 +-
 .../views/collections/CollectionCarouselView.scss  |  1 +
 .../views/collections/CollectionCarouselView.tsx   |  1 -
 .../views/collections/CollectionLinearView.tsx     | 10 +--
 src/client/views/collections/CollectionSubView.tsx |  5 +-
 .../views/collections/CollectionTreeView.tsx       | 35 ++++-----
 src/client/views/collections/CollectionView.tsx    |  5 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 .../views/nodes/ContentFittingDocumentView.tsx     | 24 +-----
 src/client/views/nodes/DocumentView.tsx            | 36 ++-------
 src/client/views/nodes/FormattedTextBox.tsx        | 11 ---
 src/client/views/nodes/ImageBox.tsx                |  9 ++-
 src/new_fields/Doc.ts                              | 90 +++++++++-------------
 src/new_fields/documentSchemas.ts                  | 12 ++-
 .../authentication/models/current_user_utils.ts    |  8 +-
 18 files changed, 96 insertions(+), 180 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8304c2a7f..adcdf260d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -22,7 +22,6 @@ import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField }
 import { HtmlField } from "../../new_fields/HtmlField";
 import { List } from "../../new_fields/List";
 import { Cast, NumCast } from "../../new_fields/Types";
-import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { dropActionType } from "../util/DragManager";
@@ -53,7 +52,6 @@ import { InkingStroke } from "../views/InkingStroke";
 import { InkField } from "../../new_fields/InkField";
 import { InkingControl } from "../views/InkingControl";
 import { RichTextField } from "../../new_fields/RichTextField";
-import { Networking } from "../Network";
 import { extname } from "path";
 import { MessageStore } from "../../server/Message";
 const requestImageSize = require('../util/request-image-size');
@@ -92,7 +90,6 @@ export interface DocumentOptions {
     scale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
-    preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
     layout?: string | Doc;
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
@@ -109,7 +106,6 @@ export interface DocumentOptions {
     curPage?: number;
     currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
-    documentText?: string;
     borderRounding?: string;
     boxShadow?: string;
     sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views
@@ -124,15 +120,16 @@ export interface DocumentOptions {
     onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
+    dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
-    clipboard?: Doc; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
+    clipboard?: Doc;
     icon?: string;
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
-    dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     strokeWidth?: number;
     color?: string;
+    treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
     treeViewHideTitle?: boolean; // whether to hide the title of a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
     treeViewOpen?: boolean; // whether this document is expanded in a tree view
@@ -141,9 +138,9 @@ export interface DocumentOptions {
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
-    isExpanded?: boolean; // is linear view expanded
-    textTransform?: string; // is linear view expanded
-    letterSpacing?: string; // is linear view expanded
+    linearViewIsExpanded?: boolean; // is linear view expanded
+    textTransform?: string;
+    letterSpacing?: string;
 }
 
 class EmptyBox {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index e572f0fcb..2877d5fd7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -134,7 +134,6 @@ export namespace DragManager {
         embedDoc?: boolean;
         moveDocument?: MoveFunction;
         isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
-        applyAsTemplate?: boolean;
     }
     export class LinkDragData {
         constructor(linkSourceDoc: Doc) {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 6b8762a5e..de0f46202 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -121,8 +121,7 @@ export class RichTextRules {
             new InputRule(
                 new RegExp(/##$/),
                 (state, match, start, end) => {
-                    const schemaDoc = Doc.GetDataDoc(this.Document);
-                    const textDoc = Doc.GetProto(Cast(schemaDoc[DataSym], Doc, null)!);
+                    const textDoc = this.Document[DataSym];
                     const numInlines = NumCast(textDoc.inlineTextCount);
                     textDoc.inlineTextCount = numInlines + 1;
                     const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 401a4b15c..192f3b8fb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -482,12 +482,13 @@ export class MainView extends React.Component {
         return new Transform(-translateX, -translateY, 1 / scale);
     }
     @computed get docButtons() {
-        if (CurrentUserUtils.UserDocument?.expandingButtons instanceof Doc) {
+        const expandingBtns = Doc.UserDoc()?.expandingButtons;
+        if (expandingBtns instanceof Doc) {
             return <div className="mainView-docButtons" ref={this._docBtnRef}
-                style={{ height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
+                style={{ height: !expandingBtns.linearViewIsExpanded ? "42px" : undefined }} >
                 <MainViewNotifs />
                 <CollectionLinearView
-                    Document={CurrentUserUtils.UserDocument.expandingButtons}
+                    Document={expandingBtns}
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     fieldKey={"data"}
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index ad369bbff..fd1296286 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -1,6 +1,7 @@
 
 .collectionCarouselView-outer {
     background: gray;
+    height : 100%;
     .collectionCarouselView-caption {
         margin-left: 10%;
         margin-right: 10%;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index f462e20b4..3c86bd799 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -12,7 +12,6 @@ import { CollectionSubView } from './CollectionSubView';
 import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
 import { Doc } from '../../../new_fields/Doc';
 import { FormattedTextBox } from '../nodes/FormattedTextBox';
-import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 67062ae41..7eb316cf0 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     componentDidMount() {
         // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
-        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
-            () => this.props.Document._width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.linearViewIsExpanded ? 1 : 0),
+            () => this.props.Document._width = 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
             { fireImmediately: true }
         );
 
@@ -84,8 +84,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         const guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
-                    onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
+                    onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
                 <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
 
                 <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25) }}>
@@ -97,7 +97,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
                             style={{
                                 width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
-                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
+                                height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
                             }}  >
                             <DocumentView
                                 Document={pair.layout}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 343be1b58..0963e1ea6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -159,7 +159,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
-            if (docDragData && !docDragData.applyAsTemplate) {
+            if (docDragData) {
                 if (de.altKey && docDragData.draggedDocuments.length) {
                     this.childDocs.map(doc => {
                         doc.layout_fromParent = docDragData.draggedDocuments[0];
@@ -253,7 +253,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             }
                         });
                     } else {
-                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300, documentText: text });
+                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+                        Doc.GetProto(htmlDoc)["data-text"] = text;
                         this.props.addDocument(htmlDoc);
                     }
                     return;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e91c3cc49..a160dcc62 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -61,7 +61,7 @@ export interface TreeViewProps {
     parentKey: string;
     active: (outsideReaction?: boolean) => boolean;
     treeViewHideHeaderFields: () => boolean;
-    preventTreeViewOpen: boolean;
+    treeViewPreventOpen: boolean;
     renderedIds: string[];
     onCheckedClick?: ScriptField;
 }
@@ -84,7 +84,7 @@ library.add(faPlus, faMinus);
  * 
  * special fields:
  * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
- * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ * treeViewPreventOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
  * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
  */
 class TreeView extends React.Component<TreeViewProps> {
@@ -96,8 +96,8 @@ class TreeView extends React.Component<TreeViewProps> {
 
     get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
     @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
-    set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
-    @computed get treeViewOpen() { return (!this.props.preventTreeViewOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
+    set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
+    @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
     @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
     @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
@@ -179,8 +179,7 @@ class TreeView extends React.Component<TreeViewProps> {
         SetValue={undoBatch((value: string) => Doc.SetInPlace(this.props.document, key, value, false) || true)}
         OnFillDown={undoBatch((value: string) => {
             Doc.SetInPlace(this.props.document, key, value, false);
-            const layoutDoc = this.props.document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layout_custom)) : undefined;
-            const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
+            const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
             EditableView.loadId = doc[Id];
             return this.props.addDocument(doc);
         })}
@@ -291,7 +290,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
-                    this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen,
+                    this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                     [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
             } else {
                 contentElement = <EditableView
@@ -334,7 +333,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
                         this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
-                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen,
+                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                         [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
@@ -464,7 +463,7 @@ class TreeView extends React.Component<TreeViewProps> {
         ChromeHeight: undefined | (() => number),
         renderDepth: number,
         treeViewHideHeaderFields: () => boolean,
-        preventTreeViewOpen: boolean,
+        treeViewPreventOpen: boolean,
         renderedIds: string[],
         libraryPath: Doc[] | undefined,
         onCheckedClick: ScriptField | undefined
@@ -574,7 +573,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 parentKey={key}
                 active={active}
                 treeViewHideHeaderFields={treeViewHideHeaderFields}
-                preventTreeViewOpen={preventTreeViewOpen}
+                treeViewPreventOpen={treeViewPreventOpen}
                 renderedIds={renderedIds} />;
         });
     }
@@ -601,8 +600,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
 
     @action
     remove = (document: Document): boolean => {
-        const targetDataDoc = this.props.Document && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym] as Doc);
-        const children = Cast(targetDataDoc[this.props.fieldKey], listSpec(Doc), []);
+        const children = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
         if (children.indexOf(document) !== -1) {
             children.splice(children.indexOf(document), 1);
             return true;
@@ -611,10 +609,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
     @action
     addDoc = (doc: Document, relativeTo: Opt<Doc>, before?: boolean): boolean => {
-        const doAddDoc = () => {
-            const targetDataDoc = this.props.Document && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym] as Doc);
-            Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc, relativeTo, before, false, false, false);
-        };
+        const doAddDoc = () =>
+            Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false);
         if (this.props.Document.resolvedDataDoc instanceof Promise) {
             this.props.Document.resolvedDataDoc.then(resolved => doAddDoc());
         } else {
@@ -637,7 +633,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         } else {
             const layoutItems: ContextMenuProps[] = [];
-            layoutItems.push({ description: (this.props.Document.preventTreeViewOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
+            layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
             ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
@@ -733,8 +729,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
                     OnFillDown={undoBatch((value: string) => {
                         Doc.SetInPlace(this.dataDoc, "title", value, false);
-                        const layoutDoc = this.props.Document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layout_custom)) : undefined;
-                        const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
+                        const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         EditableView.loadId = doc[Id];
                         this.addDoc(doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
                     })} />)}
@@ -744,7 +739,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
                             this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields),
-                            BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
+                            BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
                     }
                 </ul>
             </div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ad39b69d8..39ce4d6e8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = this.props.Document.resolvedDataDoc && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym]);
+        const targetDataDoc = this.props.Document[DataSym];
         targetDataDoc[this.props.fieldKey] = new List<Doc>([...DocListCast(targetDataDoc[this.props.fieldKey]), doc]);  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
         // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
@@ -145,8 +145,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     removeDocument(doc: Doc): boolean {
         const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
-        const targetDataDoc = this.props.Document.resolvedDataDoc && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym]);
-        const value = Cast(targetDataDoc[this.props.fieldKey], listSpec(Doc), []);
+        const value = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7f1817a15..e1854fc2d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1074,7 +1074,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
         // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
-        // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
@@ -1086,7 +1085,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
                 height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
-            {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
+            {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
         </div>;
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 671f5b96e..5c449026e 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,19 +1,17 @@
 import React = require("react");
-import { action, computed } from "mobx";
+import { computed } from "mobx";
 import { observer } from "mobx-react";
 import "react-table/react-table.css";
 import { Doc, Opt } from "../../../new_fields/Doc";
-import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { ScriptField } from "../../../new_fields/ScriptField";
 import { NumCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
 import { emptyFunction, returnEmptyString, returnOne } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
+import { CollectionView } from "../collections/CollectionView";
 import '../DocumentDecorations.scss';
 import { DocumentView } from "../nodes/DocumentView";
 import "./ContentFittingDocumentView.scss";
-import { CollectionView } from "../collections/CollectionView";
-import { TraceMobx } from "../../../new_fields/util";
 
 interface ContentFittingDocumentViewProps {
     Document?: Doc;
@@ -55,20 +53,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     }
     private contentScaling = () => this.scaling;
 
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        const docDragData = de.complete.docDragData;
-        if (docDragData) {
-            this.props.childDocs && this.props.childDocs.map(otherdoc => {
-                const target = Doc.GetProto(otherdoc);
-                target.layout = ComputedField.MakeFunction("this.image_data[0]");
-                target.layout_custom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
-            });
-            e.stopPropagation();
-        }
-        return true;
-    }
     private PanelWidth = () => this.panelWidth;
     private PanelHeight = () => this.panelHeight;;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 48df1ea33..e9b4f9f7f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,7 +92,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _downY: number = 0;
     private _lastTap: number = 0;
     private _doubleTap = false;
-    private _hitTemplateDrag = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
@@ -196,14 +195,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
     }
 
-    startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) {
+    startDragging(x: number, y: number, dropAction: dropActionType) {
         if (this._mainCont.current) {
             const dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.moveDocument = this.props.moveDocument;//  this.Document.onDragStart ? undefined : this.props.moveDocument;
-            dragData.applyAsTemplate = applyAsTemplate;
             dragData.dragDivName = this.props.dragDivName;
             this.props.Document.sourceContext = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
@@ -257,8 +255,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let preventDefault = true;
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                if (StrCast(fullScreenAlias.layoutKey) !== "layout_custom" && fullScreenAlias.layout_custom !== undefined) {
-                    fullScreenAlias.layoutKey = "layout_custom";
+                if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+                    fullScreenAlias.layoutKey = "layout_fullScreen";
                 }
                 this.props.addDocTab(fullScreenAlias, undefined, "inTab");
                 SelectionManager.DeselectAll();
@@ -323,7 +321,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
                     this.cleanUpInteractions();
-                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -434,14 +432,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) {
             this._downX = e.clientX;
             this._downY = e.clientY;
-            this._hitTemplateDrag = false;
-            // this whole section needs to move somewhere else.  We're trying to initiate a special "template" drag where
-            // this document is the template and we apply it to whatever we drop it on.
-            for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
-                if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
-                    this._hitTemplateDrag = true;
-                }
-            }
             if ((this.active || this.Document.onDragStart || this.onClickHandler) &&
                 !e.ctrlKey &&
                 (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
@@ -470,7 +460,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -556,17 +546,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @undoBatch
-    makeSelBtnClicked = (): void => {
-        if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) {
-            this.Document.isButton = false;
-            this.Document.ignoreClick = false;
-            this.Document.onClick = undefined;
-        } else {
-            this.props.Document.isButton = "Selector";
-        }
-    }
-
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -578,10 +557,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc },
                 `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
         }
-        if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
-            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom", undefined);
-            e.stopPropagation();
-        }
         if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
@@ -697,7 +672,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         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.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" });
-        onClicks.push({ description: this.props.Document.isButton ? "Remove Select Link Behavior" : "Select Link", event: this.makeSelBtnClicked, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7fbee8881..2fb61f9db 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -250,17 +250,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
                     e.stopPropagation();
                 }
-                // apply as template when dragging with Meta
-            } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) {
-                draggedDoc.isTemplateDoc = true;
-                let newLayout = Doc.Layout(draggedDoc);
-                if (typeof (draggedDoc.layout) === "string") {
-                    newLayout = Doc.MakeDelegate(draggedDoc);
-                    newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${this.props.fieldKey}'}`);
-                }
-                this.Document.layout_custom = newLayout;
-                this.Document.layoutKey = "layout_custom";
-                e.stopPropagation();
                 // embed document when dragging with a userDropAction or an embedDoc flag set
             } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
                 const target = de.complete.docDragData.droppedDocuments[0];
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c0e102195..364bce7a8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -92,10 +92,11 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             } else if (de.altKey || !this.dataDoc[this.props.fieldKey]) {
                 const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
                 const targetField = Doc.LayoutFieldKey(layoutDoc);
-                if (layoutDoc?.[DataSym][targetField] instanceof ImageField) {
-                    this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(layoutDoc[DataSym][targetField] as ImageField);
-                    this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(layoutDoc[DataSym][targetField + "-nativeWidth"]);
-                    this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(layoutDoc[DataSym][targetField + "-nativeHeight"]);
+                const targetDoc = layoutDoc[DataSym];
+                if (targetDoc[targetField] instanceof ImageField) {
+                    this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
+                    this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(targetDoc[targetField + "-nativeWidth"]);
+                    this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(targeDoc[targetField + "-nativeHeight"]);
                     e.stopPropagation();
                 }
             }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 2305f2dce..55c0660c0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -17,7 +17,6 @@ import { intersectRect } from "../Utils";
 import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { computedFn } from "mobx-utils";
 import { RichTextField } from "./RichTextField";
-import { Script } from "vm";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -112,7 +111,12 @@ export class Doc extends RefField {
             get: getter,
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
             has: (target, key) => key in target.__fields,
-            ownKeys: target => Object.keys(target.__allfields),
+            ownKeys: target => {
+                let obj = {} as any;
+                Object.assign(obj, target.___fields);
+                runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
+                return Object.keys(obj)
+            },
             getOwnPropertyDescriptor: (target, prop) => {
                 if (prop.toString() === "__LAYOUT__") {
                     return Reflect.getOwnPropertyDescriptor(target, prop);
@@ -140,17 +144,7 @@ export class Doc extends RefField {
     [key: string]: FieldResult;
 
     @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
-    private get __fields() {
-        return this.___fields;
-    }
-    private get __allfields() {
-        let obj = {} as any;
-        Object.assign(obj, this.___fields);
-        runInAction(() => obj.__LAYOUT__ = this.__LAYOUT__);
-        return obj;
-    }
-
-
+    private get __fields() { return this.___fields; }
     private set __fields(value) {
         this.___fields = value;
         for (const key in value) {
@@ -168,18 +162,19 @@ export class Doc extends RefField {
     private [UpdatingFromServer]: boolean = false;
 
     private [Update] = (diff: any) => {
-        if (this[UpdatingFromServer]) {
-            return;
-        }
-        DocServer.UpdateField(this[Id], diff);
+        !this[UpdatingFromServer] && DocServer.UpdateField(this[Id], diff);
     }
 
     private [Self] = this;
     private [SelfProxy]: any;
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
-    public get [DataSym]() { return Cast(Doc.Layout(this[SelfProxy]).resolvedDataDoc, Doc, null) || this[SelfProxy]; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
+    public get [DataSym]() {
+        const self = this[SelfProxy];
+        return self.resolvedDataDoc && !self.isTemplateForField ? self :
+            Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+    }
     @computed get __LAYOUT__() {
         const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
         if (templateLayoutDoc) {
@@ -195,12 +190,8 @@ export class Doc extends RefField {
         return undefined;
     }
 
-    [ToScriptString]() {
-        return "invalid";
-    }
-    [ToString]() {
-        return "Doc";
-    }
+    [ToScriptString]() { return "invalid"; }
+    [ToString]() { return "Doc"; }
 
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
     public static CurrentUserEmail: string = "";
@@ -287,8 +278,7 @@ export namespace Doc {
 
     export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
         try {
-            const self = doc[Self];
-            return getField(self, key, ignoreProto);
+            return getField(doc[Self], key, ignoreProto);
         } catch  {
             return doc;
         }
@@ -357,13 +347,12 @@ export namespace Doc {
         return r || r2 || r3 || r4;
     }
 
-    // gets the document's prototype or returns the document if it is a prototype
-    export function GetProto(doc: Doc) {
-        return doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
-    }
-    export function GetDataDoc(doc: Doc): Doc {
-        const proto = Doc.GetProto(doc);
-        return proto === doc ? proto : Doc.GetDataDoc(proto);
+    // Gets the data document for the document.  Note: this is mis-named -- it does not specifically
+    // return the doc's proto, but rather recursively searches through the proto inheritance chain 
+    // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
+    export function GetProto(doc: Doc): Doc {
+        const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
+        return proto === doc ? proto : Doc.GetProto(proto);
     }
 
     export function allKeys(doc: Doc): string[] {
@@ -443,11 +432,6 @@ export namespace Doc {
         return bounds;
     }
 
-    export function MakeTitled(title: string) {
-        const doc = new Doc();
-        doc.title = title;
-        return doc;
-    }
     export function MakeAlias(doc: Doc, id?: string) {
         const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         const layout = Doc.LayoutField(alias);
@@ -488,7 +472,7 @@ export namespace Doc {
         if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
 
             expandedTemplateLayout = undefined;
-        } else if (templateLayoutDoc.resolvedDataDoc === Doc.GetDataDoc(targetDoc)) {
+        } else if (templateLayoutDoc.resolvedDataDoc === Doc.GetProto(targetDoc)) {
             expandedTemplateLayout = templateLayoutDoc;
         } else if (expandedTemplateLayout === undefined) {
             setTimeout(action(() => {
@@ -497,7 +481,7 @@ export namespace Doc {
                     newLayoutDoc.lockedPosition = true;
                     newLayoutDoc.expandedTemplate = targetDoc;
                     targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
-                    const dataDoc = Doc.GetDataDoc(targetDoc);
+                    const dataDoc = Doc.GetProto(targetDoc);
                     newLayoutDoc.resolvedDataDoc = dataDoc;
                     if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && Cast(templateLayoutDoc[templateField], listSpec(Doc), []).length) {
                         dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc: templateLayoutDoc });
@@ -660,10 +644,6 @@ export namespace Doc {
         // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
         Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc);
 
-        if (templateField.backgroundColor !== templateDoc?.defaultBackgroundColor) {
-            templateField.defaultBackgroundColor = templateField.backgroundColor;
-        }
-
         return true;
     }
 
@@ -716,26 +696,26 @@ export namespace Doc {
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
     export function IsBrushed(doc: Doc) {
         return computedFn(function IsBrushed(doc: Doc) {
-            return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(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 brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0;
+        return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
         return computedFn(function IsBrushDegree(doc: Doc) {
-            return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0;
+            return Doc.IsBrushedDegreeUnmemoized(doc);
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
         brushManager.BrushedDoc.set(doc, true);
-        brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true);
+        brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
         brushManager.BrushedDoc.delete(doc);
-        brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc));
+        brushManager.BrushedDoc.delete(Doc.GetProto(doc));
         return doc;
     }
 
@@ -748,14 +728,14 @@ export namespace Doc {
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
     }
 
-    let dt = 0;
+    let _lastDate = 0;
     export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
         linkFollowUnhighlight();
         Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
         document.addEventListener("pointerdown", linkFollowUnhighlight);
-        const x = dt = Date.now();
-        window.setTimeout(() => dt === x && linkFollowUnhighlight(), 5000);
+        const lastDate = _lastDate = Date.now();
+        window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
     }
 
     export class HighlightBrush {
@@ -763,18 +743,18 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc));
+        return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
         runInAction(() => {
             highlightManager.HighlightedDoc.set(doc, true);
-            dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true);
+            dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true);
         });
     }
     export function UnHighlightDoc(doc: Doc) {
         runInAction(() => {
             highlightManager.HighlightedDoc.set(doc, false);
-            highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), false);
+            highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false);
         });
     }
     export function UnhighlightAll() {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 81f073855..3cc05d3d5 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -4,12 +4,13 @@ import { Doc } from "./Doc";
 import { DateField } from "./DateField";
 
 export const documentSchema = createSchema({
-    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below (see layout_custom as an example)
+    type: "string",             // enumerated type of document -- should be template-specific (ie, start with an '_')
+    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
     layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
-    layout_custom: Doc,         // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey')
     title: "string",            // document title (can be on either data document or layout)
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
     childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
     _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
     _nativeHeight: "number",    // "
     _width: "number",           // width of document in its container's coordinate system
@@ -27,20 +28,18 @@ export const documentSchema = createSchema({
     opacity: "number",          // opacity of document
     creationDate: DateField,    // when the document was created
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
-    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
+    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
-    autoHeight: "boolean",      // whether the height of the document should be computed automatically based on its contents
     isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
-    type: "string",             // enumerated type of document
     treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
     treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
-    preventTreeViewOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+    treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
     currentTimecode: "number",  // current play back time of a temporal document (video / audio)
     maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
@@ -57,7 +56,6 @@ export const documentSchema = createSchema({
     fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
     xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    LODarea: "number",          // area (width*height) where CollectionFreeFormViews switch from a label to rendering contents
     letterSpacing: "string",
     textTransform: "string"
 });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 45875d455..32c375bd7 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -139,7 +139,7 @@ export class CurrentUserUtils {
     static setupThumbDoc(userDoc: Doc) {
         if (!userDoc.thumbDoc) {
             userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
-                _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, isExpanded: true, backgroundColor: "white"
+                _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white"
             });
         }
         return userDoc.thumbDoc;
@@ -181,12 +181,12 @@ export class CurrentUserUtils {
         });
 
         doc.documents = Docs.Create.TreeDocument([], {
-            title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
+            title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         // setup Recently Closed library item
         doc.recentlyClosed = Docs.Create.TreeDocument([], {
-            title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
+            title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         return Docs.Create.ButtonDocument({
@@ -255,7 +255,7 @@ export class CurrentUserUtils {
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
-            backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true,
+            backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true,
 
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
         });
-- 
cgit v1.2.3-70-g09d2


From f33ad290f1de3a01f2c4536f03f040e09771f82e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 16 Feb 2020 00:50:23 -0500
Subject: improved file upload api

---
 src/client/Network.ts                              |  14 ++-
 .../util/Import & Export/DirectoryImportBox.tsx    |  17 ++--
 src/client/views/collections/CollectionSubView.tsx |  56 +++++------
 src/new_fields/Doc.ts                              |   6 +-
 src/scraping/buxton/final/BuxtonImporter.ts        |   7 +-
 src/server/ApiManagers/GooglePhotosManager.ts      |   5 +-
 src/server/ApiManagers/UploadManager.ts            |   8 +-
 src/server/DashUploadUtils.ts                      | 107 +++++++--------------
 src/server/SharedMediaTypes.ts                     |  39 ++++++++
 src/server/database.ts                             |   6 +-
 10 files changed, 137 insertions(+), 128 deletions(-)

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

diff --git a/src/client/Network.ts b/src/client/Network.ts
index ccf60f199..6982ecf19 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -1,5 +1,6 @@
 import { Utils } from "../Utils";
 import requestPromise = require('request-promise');
+import { Upload } from "../server/SharedMediaTypes";
 
 export namespace Networking {
 
@@ -17,12 +18,21 @@ export namespace Networking {
         return requestPromise.post(options);
     }
 
-    export async function PostFormDataToServer(relativeRoute: string, formData: FormData) {
+    export async function UploadFilesToServer<T extends Upload.FileInformation = Upload.FileInformation>(files: File | File[]): Promise<Upload.FileResponse<T>[]> {
+        const formData = new FormData();
+        if (Array.isArray(files)) {
+            if (!files.length) {
+                return [];
+            }
+            files.forEach(file => formData.append(Utils.GenerateGuid(), file));
+        } else {
+            formData.append(Utils.GenerateGuid(), files);
+        }
         const parameters = {
             method: 'POST',
             body: formData
         };
-        const response = await fetch(relativeRoute, parameters);
+        const response = await fetch("/uploadFormData", parameters);
         return response.json();
     }
 
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index d04f56e57..3d8bcbab7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -22,7 +22,7 @@ import "./DirectoryImportBox.scss";
 import { Networking } from "../../Network";
 import { BatchedArray } from "array-batcher";
 import * as path from 'path';
-import { AcceptibleMedia } from "../../../server/SharedMediaTypes";
+import { AcceptibleMedia, Upload } from "../../../server/SharedMediaTypes";
 
 const unsupported = ["text/html", "text/plain"];
 
@@ -107,20 +107,21 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`);
 
         const batched = BatchedArray.from(validated, { batchSize: 15 });
-        const uploads = await batched.batchedMapAsync<any>(async (batch, collector) => {
-            const formData = new FormData();
-
+        const uploads = await batched.batchedMapAsync<Upload.FileResponse<Upload.ImageInformation>>(async (batch, collector) => {
             batch.forEach(file => {
                 sizes.push(file.size);
                 modifiedDates.push(file.lastModified);
-                formData.append(Utils.GenerateGuid(), file);
             });
-
-            collector.push(...(await Networking.PostFormDataToServer("/uploadFormData", formData)));
+            collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch)));
             runInAction(() => this.completed += batch.length);
         });
 
-        await Promise.all(uploads.map(async ({ name, type, accessPaths, exifData }) => {
+        await Promise.all(uploads.map(async response => {
+            const { source: { type }, result } = response;
+            if (result instanceof Error) {
+                return;
+            }
+            const { accessPaths, exifData } = result;
             const path = Utils.prepend(accessPaths.agnostic.client);
             const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
             const { data, error } = exifData;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0963e1ea6..e0b2d524b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
 import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
 import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
@@ -25,6 +25,7 @@ import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { Networking } from "../../Network";
 import { GestureUtils } from "../../../pen-gestures/GestureUtils";
 import { InteractionUtils } from "../../util/InteractionUtils";
+import { Upload } from "../../../server/SharedMediaTypes";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc) => boolean;
@@ -288,6 +289,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
             const { items } = e.dataTransfer;
             const { length } = items;
+            const files: File[] = [];
             if (length) {
                 const batch = UndoManager.StartBatch("collection view drop");
                 const promises: Promise<void>[] = [];
@@ -307,41 +309,31 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             });
                         promises.push(prom);
                     }
-                    const type = item.type;
                     if (item.kind === "file") {
                         const file = item.getAsFile();
-                        const formData = new FormData();
-
-                        if (!file || !file.type) {
-                            continue;
-                        }
-
-                        formData.append('file', file);
-                        const dropFileName = file ? file.name : "-empty-";
-                        promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
-                            results.map(action((result: any) => {
-                                const { accessPaths, nativeWidth, nativeHeight, contentSize } = result;
-                                if (Object.keys(accessPaths).length) {
-                                    const full = { ...options, _width: 300, title: dropFileName };
-                                    const pathname = Utils.prepend(accessPaths.agnostic.client);
-                                    Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
-                                        if (doc) {
-                                            const proto = Doc.GetProto(doc);
-                                            proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
-                                            nativeWidth && (proto["data-nativeWidth"] = nativeWidth);
-                                            nativeHeight && (proto["data-nativeHeight"] = nativeHeight);
-                                            contentSize && (proto.contentSize = contentSize);
-                                            this.props?.addDocument(doc);
-                                        }
-                                    });
-                                } else {
-                                    alert("Upload failed...");
-                                }
-                            }));
-                        }));
+                        file && file.type && files.push(file);
                     }
                 }
-
+                (await Networking.UploadFilesToServer(files)).forEach(({ source: { name, type }, result }) => {
+                    if (result instanceof Error) {
+                        alert(`Upload failed: ${result.message}`);
+                        return;
+                    }
+                    const full = { ...options, _width: 300, title: name };
+                    const pathname = Utils.prepend(result.accessPaths.agnostic.client);
+                    Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
+                        if (doc) {
+                            const proto = Doc.GetProto(doc);
+                            proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+                            if (Upload.isImageInformation(result)) {
+                                proto["data-nativeWidth"] = result.nativeWidth;
+                                proto["data-nativeHeight"] = result.nativeHeight;
+                                proto.contentSize = result.contentSize;
+                            }
+                            this.props?.addDocument(doc);
+                        }
+                    });
+                });
                 if (promises.length) {
                     Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
                 } else {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 55c0660c0..a722f552e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -112,10 +112,10 @@ export class Doc extends RefField {
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
             has: (target, key) => key in target.__fields,
             ownKeys: target => {
-                let obj = {} as any;
+                const obj = {} as any;
                 Object.assign(obj, target.___fields);
                 runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
-                return Object.keys(obj)
+                return Object.keys(obj);
             },
             getOwnPropertyDescriptor: (target, prop) => {
                 if (prop.toString() === "__LAYOUT__") {
@@ -864,7 +864,7 @@ Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
 Scripting.addGlobal(function curPresentationItem() {
     const curPres = Doc.UserDoc().curPresentation as Doc;
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
-})
+});
 Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().SelectedDocs = new List([doc]); });
 Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     const docs = DocListCast(Doc.UserDoc().SelectedDocs).filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCUMENT && d.type !== DocumentType.KVP && (!excludeCollections || !Cast(d.data, listSpec(Doc), null)));
diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts
index 47d6bbe83..8041343fd 100644
--- a/src/scraping/buxton/final/BuxtonImporter.ts
+++ b/src/scraping/buxton/final/BuxtonImporter.ts
@@ -8,6 +8,7 @@ const StreamZip = require('node-stream-zip');
 const createImageSizeStream = require("image-size-stream");
 import { parseXml } from "libxmljs";
 import { strictEqual } from "assert";
+import { Readable, PassThrough } from "stream";
 
 interface DocumentContents {
     body: string;
@@ -293,15 +294,15 @@ async function writeImages(zip: any): Promise<ImageData[]> {
 
     const imageUrls: ImageData[] = [];
     for (const mediaPath of imageEntries) {
-        const streamImage = () => new Promise<any>((resolve, reject) => {
+        const streamImage = () => new Promise<Readable>((resolve, reject) => {
             zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream));
         });
 
         const { width, height, type } = await new Promise<Dimensions>(async resolve => {
-            const sizeStream = createImageSizeStream().on('size', (dimensions: Dimensions) => {
+            const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: Dimensions) => {
                 readStream.destroy();
                 resolve(dimensions);
-            });
+            }).on("error", () => readStream.destroy());
             const readStream = await streamImage();
             readStream.pipe(sizeStream);
         });
diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts
index 04b724f4b..25c54ee2e 100644
--- a/src/server/ApiManagers/GooglePhotosManager.ts
+++ b/src/server/ApiManagers/GooglePhotosManager.ts
@@ -8,6 +8,7 @@ import { Opt } from "../../new_fields/Doc";
 import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils";
 import { Database } from "../database";
 import { red } from "colors";
+import { Upload } from "../SharedMediaTypes";
 
 const prefix = "google_photos_";
 const remoteUploadError = "None of the preliminary uploads to Google's servers was successful.";
@@ -139,7 +140,7 @@ export default class GooglePhotosManager extends ApiManager {
                     return;
                 }
                 let failed = 0;
-                const completed: Opt<DashUploadUtils.ImageUploadInformation>[] = [];
+                const completed: Opt<Upload.ImageInformation>[] = [];
                 for (const { baseUrl } of mediaItems) {
                     // start by getting the content size of the remote image
                     const results = await DashUploadUtils.InspectImage(baseUrl);
@@ -151,7 +152,7 @@ export default class GooglePhotosManager extends ApiManager {
                     const { contentSize, ...attributes } = results;
                     // check to see if we have uploaded a Google user content image *specifically via this route* already
                     // that has this exact content size
-                    const found: Opt<DashUploadUtils.ImageUploadInformation> = await Database.Auxiliary.QueryUploadHistory(contentSize);
+                    const found: Opt<Upload.ImageInformation> = await Database.Auxiliary.QueryUploadHistory(contentSize);
                     if (!found) {
                         // if we haven't, then upload it locally to Dash's server
                         const upload = await DashUploadUtils.UploadInspectedImage({ contentSize, ...attributes }, undefined, prefix, false).catch(error => _error(res, downloadError, error));
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 4d09528f4..8f2a5ea3e 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -4,12 +4,12 @@ import * as formidable from 'formidable';
 import v4 = require('uuid/v4');
 const AdmZip = require('adm-zip');
 import { extname, basename, dirname } from 'path';
-import { createReadStream, createWriteStream, unlink, readFileSync } from "fs";
+import { createReadStream, createWriteStream, unlink } from "fs";
 import { publicDirectory, filesDirectory } from "..";
 import { Database } from "../database";
-import { DashUploadUtils, SizeSuffix } from "../DashUploadUtils";
+import { DashUploadUtils } from "../DashUploadUtils";
 import * as sharp from 'sharp';
-import { AcceptibleMedia } from "../SharedMediaTypes";
+import { AcceptibleMedia, Upload } from "../SharedMediaTypes";
 import { normalize } from "path";
 const imageDataUri = require('image-data-uri');
 
@@ -47,7 +47,7 @@ export default class UploadManager extends ApiManager {
                 form.keepExtensions = true;
                 return new Promise<void>(resolve => {
                     form.parse(req, async (_err, _fields, files) => {
-                        const results: any[] = [];
+                        const results: Upload.FileResponse[] = [];
                         for (const key in files) {
                             const result = await DashUploadUtils.upload(files[key]);
                             result && results.push(result);
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 913ddc1c3..b66651ef2 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -3,9 +3,9 @@ import { Utils } from '../Utils';
 import * as path from 'path';
 import * as sharp from 'sharp';
 import request = require('request-promise');
-import { ExifData, ExifImage } from 'exif';
+import { ExifImage } from 'exif';
 import { Opt } from '../new_fields/Doc';
-import { AcceptibleMedia } from './SharedMediaTypes';
+import { AcceptibleMedia, Upload } from './SharedMediaTypes';
 import { filesDirectory } from '.';
 import { File } from 'formidable';
 import { basename } from "path";
@@ -14,7 +14,7 @@ import { ParsedPDF } from "../server/PdfTypes";
 const parse = require('pdf-parse');
 import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager';
 import { red } from 'colors';
-import { Writable } from 'stream';
+import { Stream } from 'stream';
 const requestImageSize = require("../client/util/request-image-size");
 
 export enum SizeSuffix {
@@ -40,13 +40,6 @@ export namespace DashUploadUtils {
         suffix: SizeSuffix;
     }
 
-    export interface ImageFileResponse {
-        name: string;
-        path: string;
-        type: string;
-        exif: Opt<DashUploadUtils.EnrichedExifData>;
-    }
-
     export const Sizes: { [size: string]: Size } = {
         SMALL: { width: 100, suffix: SizeSuffix.Small },
         MEDIUM: { width: 400, suffix: SizeSuffix.Medium },
@@ -60,20 +53,9 @@ export namespace DashUploadUtils {
     const size = "content-length";
     const type = "content-type";
 
-    export interface ImageUploadInformation {
-        accessPaths: AccessPathInfo;
-        exifData: EnrichedExifData;
-        contentSize?: number;
-        contentType?: string;
-    }
-
-    export interface AccessPathInfo {
-        [suffix: string]: { client: string, server: string };
-    }
-
     const { imageFormats, videoFormats, applicationFormats } = AcceptibleMedia;
 
-    export async function upload(file: File): Promise<any> {
+    export async function upload(file: File): Promise<Upload.FileResponse> {
         const { type, path, name } = file;
         const types = type.split("/");
 
@@ -83,33 +65,33 @@ export namespace DashUploadUtils {
         switch (category) {
             case "image":
                 if (imageFormats.includes(format)) {
-                    const results = await UploadImage(path, basename(path));
-                    return { ...results, name, type };
+                    const result = await UploadImage(path, basename(path));
+                    return { source: file, result };
                 }
             case "video":
                 if (videoFormats.includes(format)) {
-                    return MoveParsedFile(path, Directory.videos);
+                    return MoveParsedFile(file, Directory.videos);
                 }
             case "application":
                 if (applicationFormats.includes(format)) {
-                    return UploadPdf(path);
+                    return UploadPdf(file);
                 }
         }
 
         console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`));
-        return { accessPaths: {} };
+        return { source: file, result: new Error(`Could not upload unsupported file (${name}) with upload type (${type}).`) };
     }
 
-    async function UploadPdf(absolutePath: string) {
-        const dataBuffer = readFileSync(absolutePath);
+    async function UploadPdf(file: File) {
+        const { path, name } = file;
+        const dataBuffer = readFileSync(path);
         const result: ParsedPDF = await parse(dataBuffer);
-        const parsedName = basename(absolutePath);
         await new Promise<void>((resolve, reject) => {
-            const textFilename = `${parsedName.substring(0, parsedName.length - 4)}.txt`;
+            const textFilename = `${name.substring(0, name.length - 4)}.txt`;
             const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
             writeStream.write(result.text, error => error ? reject(error) : resolve());
         });
-        return MoveParsedFile(absolutePath, Directory.pdfs);
+        return MoveParsedFile(file, Directory.pdfs);
     }
 
     /**
@@ -123,13 +105,13 @@ export namespace DashUploadUtils {
      * @param {string} prefix is a string prepended to the generated image name in the
      * event that @param filename is not specified
      * 
-     * @returns {ImageUploadInformation} This method returns
+     * @returns {ImageUploadInformation | Error} This method returns
      * 1) the paths to the uploaded images (plural due to resizing)
-     * 2) the file name of each of the resized images
+     * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed
      * 3) the size of the image, in bytes (4432130)
      * 4) the content type of the image, i.e. image/(jpeg | png | ...)
      */
-    export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise<ImageUploadInformation | Error> => {
+    export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise<Upload.ImageInformation | Error> => {
         const metadata = await InspectImage(source);
         if (metadata instanceof Error) {
             return metadata;
@@ -137,22 +119,6 @@ export namespace DashUploadUtils {
         return UploadInspectedImage(metadata, filename || metadata.filename, prefix);
     };
 
-    export interface InspectionResults {
-        source: string;
-        requestable: string;
-        exifData: EnrichedExifData;
-        contentSize: number;
-        contentType: string;
-        nativeWidth: number;
-        nativeHeight: number;
-        filename?: string;
-    }
-
-    export interface EnrichedExifData {
-        data: ExifData;
-        error?: string;
-    }
-
     export async function buildFileDirectories() {
         const pending = Object.keys(Directory).map(sub => createIfNotExists(`${filesDirectory}/${sub}`));
         return Promise.all(pending);
@@ -175,7 +141,7 @@ export namespace DashUploadUtils {
      * 
      * @param source is the path or url to the image in question
      */
-    export const InspectImage = async (source: string): Promise<InspectionResults | Error> => {
+    export const InspectImage = async (source: string): Promise<Upload.InspectionResults | Error> => {
         let rawMatches: RegExpExecArray | null;
         let filename: string | undefined;
         if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) {
@@ -216,14 +182,17 @@ export namespace DashUploadUtils {
         };
     };
 
-    export async function MoveParsedFile(absolutePath: string, destination: Directory): Promise<Opt<{ accessPaths: AccessPathInfo }>> {
+    export async function MoveParsedFile(file: File, destination: Directory): Promise<Upload.FileResponse> {
+        const { name, path: sourcePath } = file;
         return new Promise(resolve => {
-            const filename = basename(absolutePath);
-            const destinationPath = serverPathToFile(destination, filename);
-            rename(absolutePath, destinationPath, error => {
-                resolve(error ? undefined : {
-                    accessPaths: {
-                        agnostic: getAccessPaths(destination, filename)
+            const destinationPath = serverPathToFile(destination, name);
+            rename(sourcePath, destinationPath, error => {
+                resolve({
+                    source: file,
+                    result: error ? error : {
+                        accessPaths: {
+                            agnostic: getAccessPaths(destination, name)
+                        }
                     }
                 });
             });
@@ -237,16 +206,16 @@ export namespace DashUploadUtils {
         };
     }
 
-    export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise<ImageUploadInformation> => {
+    export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise<Upload.ImageInformation> => {
         const { requestable, source, ...remaining } = metadata;
         const extension = `.${remaining.contentType.split("/")[1].toLowerCase()}`;
         const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}${extension}`;
         const { images } = Directory;
-        const information: ImageUploadInformation = {
+        const information: Upload.ImageInformation = {
             accessPaths: {
                 agnostic: getAccessPaths(images, resolved)
             },
-            ...remaining
+            ...metadata
         };
         const outputPath = pathToDirectory(Directory.images);
         const writtenFiles = await outputResizedImages(() => request(requestable), outputPath, resolved, extension);
@@ -259,9 +228,9 @@ export namespace DashUploadUtils {
         return information;
     };
 
-    const parseExifData = async (source: string): Promise<EnrichedExifData> => {
+    const parseExifData = async (source: string): Promise<Upload.EnrichedExifData> => {
         const image = await request.get(source, { encoding: null });
-        return new Promise<EnrichedExifData>(resolve => {
+        return new Promise(resolve => {
             new ExifImage({ image }, (error, data) => {
                 let reason: Opt<string> = undefined;
                 if (error) {
@@ -279,18 +248,14 @@ export namespace DashUploadUtils {
         force: true
     };
 
-    export interface ReadStreamLike {
-        pipe: (dest: Writable) => Writable;
-    }
-
-    export async function outputResizedImages(readStreamSource: () => ReadStreamLike | Promise<ReadStreamLike>, outputPath: string, fileName: string, ext: string) {
+    export async function outputResizedImages(readStreamSource: () => Stream | Promise<Stream>, outputPath: string, fileName: string, ext: string) {
         const writtenFiles: { [suffix: string]: string } = {};
         for (const { resizer, suffix } of resizers(ext)) {
             const resolved = writtenFiles[suffix] = InjectSize(fileName, suffix);
             await new Promise<void>(async (resolve, reject) => {
                 const writeStream = createWriteStream(path.resolve(outputPath, resolved));
-                let readStream: ReadStreamLike;
                 const source = readStreamSource();
+                let readStream: Stream;
                 if (source instanceof Promise) {
                     readStream = await source;
                 } else {
@@ -320,7 +285,7 @@ export namespace DashUploadUtils {
                     initial = initial.webp();
                 } else if (tiffs.includes(ext)) {
                     initial = initial.tiff();
-                } else {
+                } else if (ext === ".gif") {
                     initial = undefined;
                 }
                 return {
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 274b4f01e..185e787cc 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -1,3 +1,6 @@
+import { ExifData } from 'exif';
+import { File } from 'formidable';
+
 export namespace AcceptibleMedia {
     export const gifs = [".gif"];
     export const pngs = [".png"];
@@ -7,4 +10,40 @@ export namespace AcceptibleMedia {
     export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs];
     export const videoFormats = [".mov", ".mp4"];
     export const applicationFormats = [".pdf"];
+}
+
+export namespace Upload {
+
+    export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
+        return "nativeWidth" in uploadResponse;
+    }
+
+    export interface FileInformation {
+        accessPaths: AccessPathInfo;
+    }
+
+    export type FileResponse<T extends FileInformation = FileInformation> = { source: File, result: T | Error };
+
+    export type ImageInformation = FileInformation & InspectionResults;
+
+    export interface AccessPathInfo {
+        [suffix: string]: { client: string, server: string };
+    }
+
+    export interface InspectionResults {
+        source: string;
+        requestable: string;
+        exifData: EnrichedExifData;
+        contentSize: number;
+        contentType: string;
+        nativeWidth: number;
+        nativeHeight: number;
+        filename?: string;
+    }
+
+    export interface EnrichedExifData {
+        data: ExifData;
+        error?: string;
+    }
+
 }
\ No newline at end of file
diff --git a/src/server/database.ts b/src/server/database.ts
index 83ce865c6..055f04c49 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -2,12 +2,12 @@ import * as mongodb from 'mongodb';
 import { Transferable } from './Message';
 import { Opt } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
-import { DashUploadUtils } from './DashUploadUtils';
 import { Credentials } from 'google-auth-library';
 import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
 import { IDatabase } from './IDatabase';
 import { MemoryDatabase } from './MemoryDatabase';
 import * as mongoose from 'mongoose';
+import { Upload } from './SharedMediaTypes';
 
 export namespace Database {
 
@@ -297,7 +297,7 @@ export namespace Database {
         };
 
         export const QueryUploadHistory = async (contentSize: number) => {
-            return SanitizedSingletonQuery<DashUploadUtils.ImageUploadInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
+            return SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
         };
 
         export namespace GoogleAuthenticationToken {
@@ -326,7 +326,7 @@ export namespace Database {
 
         }
 
-        export const LogUpload = async (information: DashUploadUtils.ImageUploadInformation) => {
+        export const LogUpload = async (information: Upload.ImageInformation) => {
             const bundle = {
                 _id: Utils.GenerateDeterministicGuid(String(information.contentSize!)),
                 ...information
-- 
cgit v1.2.3-70-g09d2


From bd46e2c0ee4260b341af5dba2efba7511c4ea829 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 16 Feb 2020 00:56:56 -0500
Subject: clean up

---
 src/client/views/collections/CollectionSubView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e0b2d524b..e80e1c802 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -314,7 +314,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                         file && file.type && files.push(file);
                     }
                 }
-                (await Networking.UploadFilesToServer(files)).forEach(({ source: { name, type }, result }) => {
+                promises.push(Networking.UploadFilesToServer(files).then(responses => responses.forEach(({ source: { name, type }, result }) => {
                     if (result instanceof Error) {
                         alert(`Upload failed: ${result.message}`);
                         return;
@@ -333,7 +333,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             this.props?.addDocument(doc);
                         }
                     });
-                });
+                })));
                 if (promises.length) {
                     Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
                 } else {
-- 
cgit v1.2.3-70-g09d2


From b42e1e1e2da941955d7751b6003f18fecd5f2f8d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 16 Feb 2020 12:22:10 -0500
Subject: collection sub view and google authentication manager cleanup,
 deleteAssets route added

---
 src/client/apis/GoogleAuthenticationManager.tsx    |  46 ++--
 .../views/collections/CollectionCarouselView.tsx   |  28 +--
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   7 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx | 264 +++++++++++----------
 .../views/collections/CollectionTreeView.tsx       |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  10 +-
 .../CollectionMulticolumnView.tsx                  |   6 +-
 .../CollectionMultirowView.tsx                     |   6 +-
 src/server/ApiManagers/DeleteManager.ts            |  31 +--
 13 files changed, 213 insertions(+), 209 deletions(-)

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

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index ce1277667..417dc3c3b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -12,8 +12,8 @@ const prompt = "Paste authorization code here...";
 @observer
 export default class GoogleAuthenticationManager extends React.Component<{}> {
     public static Instance: GoogleAuthenticationManager;
-    @observable private openState = false;
     private authenticationLink: Opt<string> = undefined;
+    @observable private openState = false;
     @observable private authenticationCode: Opt<string> = undefined;
     @observable private clickedState = false;
     @observable private success: Opt<boolean> = undefined;
@@ -39,24 +39,18 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
                 const disposer = reaction(
                     () => this.authenticationCode,
                     async authenticationCode => {
-                        if (!authenticationCode) {
-                            return;
+                        if (authenticationCode) {
+                            disposer();
+                            const { access_token, avatar, name } = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+                            runInAction(() => {
+                                this.avatar = avatar;
+                                this.username = name;
+                                this.hasBeenClicked = false;
+                                this.success = false;
+                            });
+                            this.beginFadeout();
+                            resolve(access_token);
                         }
-                        const { access_token, avatar, name } = await Networking.PostToServer(
-                            "/writeGoogleAccessToken",
-                            { authenticationCode }
-                        );
-                        runInAction(() => {
-                            this.avatar = avatar;
-                            this.username = name;
-                        });
-                        this.beginFadeout();
-                        disposer();
-                        resolve(access_token);
-                        action(() => {
-                            this.hasBeenClicked = false;
-                            this.success = false;
-                        });
                     }
                 );
             });
@@ -86,26 +80,20 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
         GoogleAuthenticationManager.Instance = this;
     }
 
-    private handleClick = () => {
-        window.open(this.authenticationLink);
-        setTimeout(() => this.hasBeenClicked = true, 500);
-    }
-
-    private handlePaste = action((e: React.ChangeEvent<HTMLInputElement>) => {
-        this.authenticationCode = e.currentTarget.value;
-    });
-
     private get renderPrompt() {
         return (
             <div className={'authorize-container'}>
                 {this.displayLauncher ? <button
                     className={"dispatch"}
-                    onClick={this.handleClick}
+                    onClick={() => {
+                        window.open(this.authenticationLink);
+                        setTimeout(() => this.hasBeenClicked = true, 500);
+                    }}
                     style={{ marginBottom: this.clickedState ? 15 : 0 }}
                 >Authorize a Google account...</button> : (null)}
                 {this.clickedState ? <input
                     className={'paste-target'}
-                    onChange={this.handlePaste}
+                    onChange={action(e => this.authenticationCode = e.currentTarget.value)}
                     placeholder={prompt}
                 /> : (null)}
                 {this.avatar ? <img
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 8167416c6..a0cb1fe19 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -27,7 +27,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer?.();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
@@ -41,21 +41,21 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     }
 
     panelHeight = () => this.props.PanelHeight() - 50;
-    @computed get content() { 
+    @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
         return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
-        <>
-            <div className="collectionCarouselView-image" key="image">
-                <ContentFittingDocumentView {...this.props}
-                    Document={this.childLayoutPairs[index].layout}
-                    DataDocument={this.childLayoutPairs[index].data}
-                    PanelHeight={this.panelHeight}
-                    getTransform={this.props.ScreenToLocalTransform} />
-            </div>
-            <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}>
-                <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
-            </div>
-        </>
+            <>
+                <div className="collectionCarouselView-image" key="image">
+                    <ContentFittingDocumentView {...this.props}
+                        Document={this.childLayoutPairs[index].layout}
+                        DataDocument={this.childLayoutPairs[index].data}
+                        PanelHeight={this.panelHeight}
+                        getTransform={this.props.ScreenToLocalTransform} />
+                </div>
+                <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}>
+                    <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+                </div>
+            </>;
     }
     @computed get buttons() {
         return <>
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7eb316cf0..9bbc9f1b6 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -67,7 +67,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index e25a2f5eb..f3d512a97 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -80,7 +80,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
             const castedValue = this.getValue(this._heading);
             de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
-            this.props.parent.drop(e, de);
+            this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
         }
     });
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index fa8be5177..c422c38f1 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -14,7 +14,6 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
@@ -175,7 +174,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             moveDocument={this.props.moveDocument}
             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
             active={this.props.active}
-            onDrop={this.onDrop}
+            onDrop={this.onExternalDrop}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}
             isSelected={this.props.isSelected}
@@ -199,7 +198,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     render() {
         return <div className="collectionSchemaView-container">
-            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}>
+            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
                 {this.schemaTable}
             </div>
             {this.dividerDragger}
@@ -692,7 +691,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
-            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" })
+            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
         }
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 055035b3e..a9cefae6a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -189,7 +189,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
-        let layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -234,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         const where = [de.x, de.y];
         let targInd = -1;
         let plusOne = 0;
@@ -248,7 +248,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
                 }
             });
-            if (super.drop(e, de)) {
+            if (super.onInternalDrop(e, de)) {
                 const newDoc = de.complete.docDragData.droppedDocuments[0];
                 const docs = this.childDocList;
                 if (docs) {
@@ -264,7 +264,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @undoBatch
     @action
-    onDrop = async (e: React.DragEvent): Promise<void> => {
+    onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
@@ -274,7 +274,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 targInd = i;
             }
         });
-        super.onDrop(e, {}, () => {
+        super.onExternalDrop(e, {}, () => {
             if (targInd !== -1) {
                 const newDoc = this.childDocs[this.childDocs.length - 1];
                 const docs = this.childDocList;
@@ -405,7 +405,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         transformOrigin: "top left",
                     }}
                     onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
-                    onDrop={this.onDrop.bind(this)}
+                    onDrop={this.onExternalDrop.bind(this)}
                     onContextMenu={this.onContextMenu}
                     onWheel={e => this.props.active() && e.stopPropagation()} >
                     {this.renderedSections}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2ff477c57..87c35679f 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -71,7 +71,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             else {
                 de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined);
             }
-            this.props.parent.drop(e, de);
+            this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
         }
     });
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e80e1c802..042385dcd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -57,7 +57,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
-                this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
@@ -156,7 +156,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         @action
-        protected drop(e: Event, de: DragManager.DropEvent): boolean {
+        protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
@@ -195,158 +195,172 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         @action
-        protected async onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
+        protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
             if (e.ctrlKey) {
                 e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
                 return;
             }
-            const html = e.dataTransfer.getData("text/html");
-            const text = e.dataTransfer.getData("text/plain");
+
+            const { dataTransfer } = e;
+            const html = dataTransfer.getData("text/html");
+            const text = dataTransfer.getData("text/plain");
 
             if (text && text.startsWith("<div")) {
                 return;
             }
+
             e.stopPropagation();
             e.preventDefault();
-
-            if (html && FormattedTextBox.IsFragment(html)) {
-                const href = FormattedTextBox.GetHref(html);
-                if (href) {
-                    const docid = FormattedTextBox.GetDocFromUrl(href);
-                    if (docid) { // prosemirror text containing link to dash document
-                        DocServer.GetRefField(docid).then(f => {
-                            if (f instanceof Doc) {
-                                if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f);
-                            }
-                        });
-                    } else {
-                        this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
-                    }
-                } else if (text) {
-                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
-                }
+            const { addDocument } = this.props;
+            if (!addDocument) {
+                alert("this.props.addDocument does not exist. Aborting drop operation.");
                 return;
             }
-            if (html && !html.startsWith("<a")) {
-                const tags = html.split("<");
-                if (tags[0] === "") tags.splice(0, 1);
-                const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
-                if (img) {
-                    const split = img.split("src=\"")[1].split("\"")[0];
-                    let source = split;
-                    if (split.startsWith("data:image") && split.includes("base64")) {
-                        const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
-                        source = Utils.prepend(accessPaths.agnostic.client);
+
+            if (html) {
+                if (FormattedTextBox.IsFragment(html)) {
+                    const href = FormattedTextBox.GetHref(html);
+                    if (href) {
+                        const docid = FormattedTextBox.GetDocFromUrl(href);
+                        if (docid) { // prosemirror text containing link to dash document
+                            DocServer.GetRefField(docid).then(f => {
+                                if (f instanceof Doc) {
+                                    if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+                                    (f instanceof Doc) && addDocument(f);
+                                }
+                            });
+                        } else {
+                            addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
+                        }
+                    } else if (text) {
+                        addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
                     }
-                    const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
-                    ImageUtils.ExtractExif(doc);
-                    this.props.addDocument(doc);
                     return;
-                } else {
-                    const path = window.location.origin + "/doc/";
-                    if (text.startsWith(path)) {
-                        const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                        DocServer.GetRefField(docid).then(f => {
-                            if (f instanceof Doc) {
-                                if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f);
-                            }
-                        });
+                }
+                if (!html.startsWith("<a")) {
+                    const tags = html.split("<");
+                    if (tags[0] === "") tags.splice(0, 1);
+                    const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+                    if (img) {
+                        const split = img.split("src=\"")[1].split("\"")[0];
+                        let source = split;
+                        if (split.startsWith("data:image") && split.includes("base64")) {
+                            const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
+                            source = Utils.prepend(accessPaths.agnostic.client);
+                        }
+                        const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
+                        ImageUtils.ExtractExif(doc);
+                        addDocument(doc);
+                        return;
                     } else {
-                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
-                        Doc.GetProto(htmlDoc)["data-text"] = text;
-                        this.props.addDocument(htmlDoc);
+                        const path = window.location.origin + "/doc/";
+                        if (text.startsWith(path)) {
+                            const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                            DocServer.GetRefField(docid).then(f => {
+                                if (f instanceof Doc) {
+                                    if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+                                    (f instanceof Doc) && this.props.addDocument(f);
+                                }
+                            });
+                        } else {
+                            const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+                            Doc.GetProto(htmlDoc)["data-text"] = text;
+                            this.props.addDocument(htmlDoc);
+                        }
+                        return;
                     }
-                    return;
                 }
             }
-            if (text && text.indexOf("www.youtube.com/watch") !== -1) {
-                const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
-                this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 }));
-                return;
-            }
-            let matches: RegExpExecArray | null;
-            if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
-                const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
-                const proto = newBox.proto!;
-                const documentId = matches[2];
-                proto[GoogleRef] = documentId;
-                proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
-                proto.backgroundColor = "#eeeeff";
-                this.props.addDocument(newBox);
-                // const parent = Docs.Create.StackingDocument([newBox], { title: `Google Doc Import (${documentId})` });
-                // CollectionDockingView.Instance.AddRightSplit(parent, undefined);
-                // proto.height = parent[HeightSym]();
-                return;
-            }
-            if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
-                const albums = await GooglePhotos.Transactions.ListAlbums();
-                const albumId = matches[3];
-                const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
-                console.log(mediaItems);
-                return;
+
+            if (text) {
+                if (text.includes("www.youtube.com/watch")) {
+                    const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+                    addDocument(Docs.Create.VideoDocument(url, {
+                        ...options,
+                        title: url,
+                        _width: 400,
+                        _height: 315,
+                        _nativeWidth: 600,
+                        _nativeHeight: 472.5
+                    }));
+                    return;
+                }
+                let matches: RegExpExecArray | null;
+                if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+                    const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
+                    const proto = newBox.proto!;
+                    const documentId = matches[2];
+                    proto[GoogleRef] = documentId;
+                    proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
+                    proto.backgroundColor = "#eeeeff";
+                    addDocument(newBox);
+                    return;
+                }
+                if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
+                    const albumId = matches[3];
+                    const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
+                    console.log(mediaItems);
+                    return;
+                }
             }
+
             const { items } = e.dataTransfer;
             const { length } = items;
             const files: File[] = [];
-            if (length) {
-                const batch = UndoManager.StartBatch("collection view drop");
-                const promises: Promise<void>[] = [];
-                // tslint:disable-next-line:prefer-for-of
-                for (let i = 0; i < length; i++) {
-                    const item = e.dataTransfer.items[i];
-                    if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
-                        let str: string;
-                        const prom = new Promise<string>(resolve => item.getAsString(resolve))
-                            .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
-                            .then(result => {
-                                const type = result["content-type"];
-                                if (type) {
-                                    Docs.Get.DocumentFromType(type, str, options)
-                                        .then(doc => doc && this.props.addDocument(doc));
-                                }
-                            });
-                        promises.push(prom);
-                    }
-                    if (item.kind === "file") {
-                        const file = item.getAsFile();
-                        file && file.type && files.push(file);
+            const generatedDocuments: Doc[] = [];
+            if (!length) {
+                alert("No uploadable content found.");
+                return;
+            }
+
+            const batch = UndoManager.StartBatch("collection view drop");
+            for (let i = 0; i < length; i++) {
+                const item = e.dataTransfer.items[i];
+                if (item.kind === "string" && item.type.includes("uri")) {
+                    const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
+                    const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+                    if (type) {
+                        const doc = await Docs.Get.DocumentFromType(type, stringContents, options);
+                        doc && generatedDocuments.push(doc);
                     }
                 }
-                promises.push(Networking.UploadFilesToServer(files).then(responses => responses.forEach(({ source: { name, type }, result }) => {
-                    if (result instanceof Error) {
-                        alert(`Upload failed: ${result.message}`);
-                        return;
-                    }
-                    const full = { ...options, _width: 300, title: name };
-                    const pathname = Utils.prepend(result.accessPaths.agnostic.client);
-                    Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
-                        if (doc) {
-                            const proto = Doc.GetProto(doc);
-                            proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
-                            if (Upload.isImageInformation(result)) {
-                                proto["data-nativeWidth"] = result.nativeWidth;
-                                proto["data-nativeHeight"] = result.nativeHeight;
-                                proto.contentSize = result.contentSize;
-                            }
-                            this.props?.addDocument(doc);
-                        }
-                    });
-                })));
-                if (promises.length) {
-                    Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
-                } else {
-                    if (text && !text.includes("https://")) {
-                        this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
-                    }
-                    batch.end();
+                if (item.kind === "file") {
+                    const file = item.getAsFile();
+                    file && file.type && files.push(file);
+                }
+            }
+            for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) {
+                if (result instanceof Error) {
+                    alert(`Upload failed: ${result.message}`);
+                    return;
                 }
+                const full = { ...options, _width: 300, title: name };
+                const pathname = Utils.prepend(result.accessPaths.agnostic.client);
+                const doc = await Docs.Get.DocumentFromType(type, pathname, full);
+                if (!doc) {
+                    continue;
+                }
+                const proto = Doc.GetProto(doc);
+                proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+                if (Upload.isImageInformation(result)) {
+                    proto["data-nativeWidth"] = result.nativeWidth;
+                    proto["data-nativeHeight"] = result.nativeHeight;
+                    proto.contentSize = result.contentSize;
+                }
+                generatedDocuments.push(doc);
+            }
+            if (generatedDocuments.length) {
+                generatedDocuments.forEach(addDocument);
+                completed && completed();
             } else {
-                alert("No uploadable content found.");
+                if (text && !text.includes("https://")) {
+                    addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
+                }
             }
+            batch.end();
         }
     }
+
     return CollectionSubView;
 }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8720ce002..13ab7c1a4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -210,7 +210,7 @@ class TreeView extends React.Component<TreeViewProps> {
             } else {
                 ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
                 ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
-            }    
+            }
             ContextMenu.Instance.addItem({ description: "Toggle Theme Colors", event: () => this.props.document.darkScheme = !this.props.document.darkScheme, icon: "minus" });
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
             ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
@@ -594,7 +594,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer && this.treedropDisposer();
         if (this._mainEle = ele) {
-            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
@@ -702,7 +702,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
     }
     outerXf = () => Utils.GetScreenTransform(this._mainEle!);
-    onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
+    onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
 
     @computed get renderClearButton() {
         return <div id="toolbar" key="toolbar">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 969d6b3c8..bdc5e03e3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -117,20 +117,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @action
-    onDrop = (e: React.DragEvent): Promise<void> => {
+    onExternalDrop = (e: React.DragEvent): Promise<void> => {
         const pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        return super.onDrop(e, { x: pt[0], y: pt[1] });
+        return super.onExternalDrop(e, { x: pt[0], y: pt[1] });
     }
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (this.props.Document.isBackground) return false;
         const xf = this.getTransform();
         const xfo = this.getTransformOverlay();
         const [xp, yp] = xf.transformPoint(de.x, de.y);
         const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
-        if (super.drop(e, de)) {
+        if (super.onInternalDrop(e, de)) {
             if (de.complete.docDragData) {
                 if (de.complete.docDragData.droppedDocuments.length) {
                     const firstDoc = de.complete.docDragData.droppedDocuments[0];
@@ -1079,7 +1079,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
-            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
+            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index db5673573..785f90212 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -189,8 +189,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
             de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
                 d.dimUnit = "*";
                 d.dimMagnitude = 1;
@@ -214,7 +214,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             getTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
-        />
+        />;
     }
     /**
      * @returns the resolved list of rendered child documents, displayed
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 630a178cf..5e59f8237 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -190,8 +190,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
             de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
                 d.dimUnit = "*";
                 d.dimMagnitude = 1;
@@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             getTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
-        />
+        />;
     }
     /**
      * @returns the resolved list of rendered child documents, displayed
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index be452c0ff..9e70af2eb 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -2,6 +2,11 @@ import ApiManager, { Registration } from "./ApiManager";
 import { Method, _permission_denied, PublicHandler } from "../RouteManager";
 import { WebSocket } from "../Websocket/Websocket";
 import { Database } from "../database";
+import rimraf = require("rimraf");
+import { pathToDirectory, Directory } from "./UploadManager";
+import { filesDirectory } from "..";
+import { DashUploadUtils } from "../DashUploadUtils";
+import { mkdirSync } from "fs";
 
 export default class DeleteManager extends ApiManager {
 
@@ -31,21 +36,19 @@ export default class DeleteManager extends ApiManager {
             }
         });
 
-        const hi: PublicHandler = async ({ res, isRelease }) => {
-            if (isRelease) {
-                return _permission_denied(res, deletionPermissionError);
+        register({
+            method: Method.GET,
+            subscription: "/deleteAssets",
+            secureHandler: async ({ res, isRelease }) => {
+                if (isRelease) {
+                    return _permission_denied(res, deletionPermissionError);
+                }
+                rimraf.sync(filesDirectory);
+                mkdirSync(filesDirectory);
+                await DashUploadUtils.buildFileDirectories();
+                res.redirect("/delete");
             }
-            await Database.Instance.deleteAll('users');
-            res.redirect("/home");
-        };
-
-        // register({
-        //     method: Method.GET,
-        //     subscription: "/deleteUsers",
-        //     onValidation: hi,
-        //     onUnauthenticated: hi
-        // });
-
+        });
 
         register({
             method: Method.GET,
-- 
cgit v1.2.3-70-g09d2


From c395bb6082884dba910e1e1a505650b2566ba4da Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 16 Feb 2020 19:00:48 -0500
Subject: mostly code simple cleanup.

---
 src/client/views/DocumentDecorations.tsx           | 16 ++--
 src/client/views/collections/CollectionSubView.tsx | 27 ++-----
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 85 +++++++++-------------
 src/client/views/nodes/DocumentView.tsx            | 20 +----
 5 files changed, 50 insertions(+), 100 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index f516bb8fc..5fc14e716 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -51,15 +51,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @observable private _titleControlString: string = "#title";
     @observable private _edtingTitle = false;
     @observable private _hidden = false;
-    @observable private _opacity = 1;
-    @observable public Interacting = false;
+    @observable private _addedCloseCalls: CloseCall[] = [];
 
+    @observable public Interacting = false;
     @observable public pushIcon: IconProp = "arrow-alt-circle-up";
     @observable public pullIcon: IconProp = "arrow-alt-circle-down";
     @observable public pullColor: string = "white";
-    @observable public openHover = false;
-    @observable private addedCloseCalls: CloseCall[] = [];
-
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -93,9 +90,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     addCloseCall = (handler: CloseCall) => {
-        const currentOffset = this.addedCloseCalls.length - 1;
-        this.addedCloseCalls.push((toBeDeleted: DocumentView[]) => {
-            this.addedCloseCalls.splice(currentOffset, 1);
+        const currentOffset = this._addedCloseCalls.length - 1;
+        this._addedCloseCalls.push((toBeDeleted: DocumentView[]) => {
+            this._addedCloseCalls.splice(currentOffset, 1);
             handler(toBeDeleted);
         });
     }
@@ -173,7 +170,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             const recent = Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc;
             const selected = SelectionManager.SelectedDocuments().slice();
             SelectionManager.DeselectAll();
-            this.addedCloseCalls.forEach(handler => handler(selected));
+            this._addedCloseCalls.forEach(handler => handler(selected));
 
             selected.map(dv => {
                 recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true);
@@ -412,7 +409,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
-                opacity: this._opacity
             }}>
                 {minimizeIcon}
                 {titleArea}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 042385dcd..bebd99a3a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -67,23 +67,24 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         }
 
         componentDidMount() {
-            this._childLayoutDisposer = reaction(() => [this.childDocs, (Cast(this.props.Document.childLayout, Doc) as Doc)?.[Id]],
-                (args) => {
-                    const childLayout = Cast(this.props.Document.childLayout, Doc);
+            this._childLayoutDisposer = reaction(() => ({ childDocs: this.childDocs, childLayout: Cast(this.props.Document.childLayout, Doc) }),
+                ({ childDocs, childLayout }) => {
                     if (childLayout instanceof Doc) {
-                        this.childDocs.map(doc => {
+                        childDocs.map(doc => {
                             doc.layout_fromParent = childLayout;
                             doc.layoutKey = "layout_fromParent";
                         });
                     }
                     else if (!(childLayout instanceof Promise)) {
-                        this.childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
+                        childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
                     }
                 }, { fireImmediately: true });
 
         }
         componentWillUnmount() {
-            this._childLayoutDisposer && this._childLayoutDisposer();
+            this.gestureDisposer?.();
+            this.multiTouchDisposer?.();
+            this._childLayoutDisposer?.();
         }
 
         @computed get dataDoc() {
@@ -96,11 +97,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
         @computed get dataField() {
-            const { annotationsKey, fieldKey } = this.props;
-            if (annotationsKey) {
-                return this.dataDoc[fieldKey + "-" + annotationsKey];
-            }
-            return this.dataDoc[fieldKey];
+            return this.dataDoc[this.props.fieldKey + (this.props.annotationsKey ? "-" + this.props.annotationsKey : "")];
         }
 
         get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
@@ -161,14 +158,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
             if (docDragData) {
-                if (de.altKey && docDragData.draggedDocuments.length) {
-                    this.childDocs.map(doc => {
-                        doc.layout_fromParent = docDragData.draggedDocuments[0];
-                        doc.layoutKey = "layout_fromParent";
-                    });
-                    e.stopPropagation();
-                    return true;
-                }
                 let added = false;
                 if (this.props.Document._freezeOnDrop) {
                     de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d363770bf..671a3e0c4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -289,7 +289,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+    const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bdc5e03e3..427a8d9fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -72,6 +72,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = observable.map<string, any>();
+    private _cachedPool: Map<string, any> = new Map();
 
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -436,15 +437,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
             PDFMenu.Instance.fadeOut(true);
-            const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
-            const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
-            const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
-            const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
-            const ranges = docs.filter(doc => doc).filter(doc => this.childDataProvider(doc)).reduce((range, doc) => {
-                const x = this.childDataProvider(doc).x;//NumCast(doc.x);
-                const y = this.childDataProvider(doc).y;//NumCast(doc.y);
-                const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
-                const ye = this.childDataProvider(doc).height + y; //y + NumCast(layoutDoc.height);
+            const minx = this.childDataProvider(docs[0]).x;
+            const miny = this.childDataProvider(docs[0]).y;
+            const maxx = this.childDataProvider(docs[0]).width + minx;
+            const maxy = this.childDataProvider(docs[0]).height + miny;
+            const ranges = docs.filter(doc => doc && this.childDataProvider(doc)).reduce((range, doc) => {
+                const x = this.childDataProvider(doc).x;
+                const y = this.childDataProvider(doc).y;
+                const xe = this.childDataProvider(doc).width + x;
+                const ye = this.childDataProvider(doc).height + y;
                 return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
                 [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
             }, [[minx, maxx], [miny, maxy]]);
@@ -743,9 +744,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return { ...result, transition: "transform 1s" };
         }
         const layoutDoc = Doc.Layout(params.doc);
+        const { x, y, z, color, zIndex } = params.doc;
         return {
-            x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), color: Cast(params.doc.color, "string"),
-            zIndex: Cast(params.doc.zIndex, "number"), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+            x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
+            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
         };
     }
 
@@ -757,55 +759,46 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
-        const x = Cast(viewDef.x, "number");
-        const y = Cast(viewDef.y, "number");
-        const z = Cast(viewDef.z, "number");
-        const highlight = Cast(viewDef.highlight, "boolean");
-        const zIndex = Cast(viewDef.zIndex, "number");
-        const color = Cast(viewDef.color, "string");
-        const width = Cast(viewDef.width, "number", null);
-        const height = Cast(viewDef.height, "number", null);
+        const { x, y, z } = viewDef;
+        const color = StrCast(viewDef.color);
+        const width = Cast(viewDef.width, "number");
+        const height = Cast(viewDef.height, "number");
+        const transform = `translate(${x}px, ${y}px)`;
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
             const fontSize = Cast(viewDef.fontSize, "number");
             return [text, x, y].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color}
-                        style={{ width, height, color, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+                    ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
                         {text}
                     </div>,
                     bounds: viewDef
                 };
         } else if (viewDef.type === "div") {
-            const backgroundColor = Cast(viewDef.color, "string");
             return [x, y].some(val => val === undefined) ? undefined :
                 {
                     ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
-                        style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
+                        style={{ width, height, backgroundColor: color, transform }} />,
                     bounds: viewDef
                 };
         }
     }
 
     childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
-        if (!doc) {
-            console.log(doc);
-        }
         return this._layoutPoolData.get(doc[Id]);
     }.bind(this));
 
-    doTimelineLayout(poolData: Map<string, any>) {
+    doTimelineLayout(poolData: Map<string, PoolData>) {
         return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
-    doPivotLayout(poolData: Map<string, any>) {
+    doPivotLayout(poolData: Map<string, PoolData>) {
         return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
-    _cachedPool: Map<string, any> = new Map();
-    doFreeformLayout(poolData: Map<string, any>) {
+    doFreeformLayout(poolData: Map<string, PoolData>) {
         const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
         const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
         const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
@@ -830,25 +823,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed get filterDocs() {
         const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
         const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
-        const clusters: { [key: string]: { [value: string]: string } } = {};
+        const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
         for (let i = 0; i < docFilters.length; i += 3) {
             const [key, value, modifiers] = docFilters.slice(i, i + 3);
-            const cluster = clusters[key];
-            if (!cluster) {
-                const child: { [value: string]: string } = {};
-                child[value] = modifiers;
-                clusters[key] = child;
-            } else {
-                cluster[value] = modifiers;
+            if (!filterFacets[key]) {
+                filterFacets[key] = {};
             }
+            filterFacets[key][value] = modifiers;
         }
         const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
-            for (const key of Object.keys(clusters)) {
-                const cluster = clusters[key];
-                const satisfiesFacet = Object.keys(cluster).some(inner => {
-                    const modifier = cluster[inner];
-                    return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
-                });
+            for (const facetKey of Object.keys(filterFacets)) {
+                const facet = filterFacets[facetKey];
+                const satisfiesFacet = Object.keys(facet).some(value =>
+                    (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
                 if (!satisfiesFacet) {
                     return false;
                 }
@@ -897,8 +884,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     componentDidMount() {
         super.componentDidMount();
-        this._layoutComputeReaction = reaction(
-            () => (this.doLayoutComputation),
+        this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
             (computation) => this._layoutElements = computation?.elements.slice() || [],
             { fireImmediately: true, name: "doLayout" });
     }
@@ -1028,7 +1014,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
-
     private childViews = () => {
         const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
         return [
@@ -1037,13 +1022,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ];
     }
 
-    // @observable private _palette?: JSX.Element;
-
     children = () => {
         const eles: JSX.Element[] = [];
         eles.push(...this.childViews());
-        // this._palette && (eles.push(this._palette));
-        // this.currentStroke && (eles.push(this.currentStroke));
         eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
         return eles;
     }
@@ -1101,7 +1082,7 @@ interface CollectionFreeFormOverlayViewProps {
 @observer
 class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
     render() {
-        return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+        return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele);
     }
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4efaef408..b4ec09aee 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,13 +1,12 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, runInAction, observable } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
 import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
-import { List } from '../../../new_fields/List';
 import { RichTextField } from '../../../new_fields/RichTextField';
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
@@ -59,8 +58,6 @@ export interface DocumentViewProps {
     LibraryPath: Doc[];
     fitToBox?: boolean;
     onClick?: ScriptField;
-    onPointerDown?: ScriptField;
-    onPointerUp?: ScriptField;
     dragDivName?: string;
     addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
@@ -107,8 +104,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
     @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
     @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
-    @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
-    @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
 
     private _firstX: number = 0;
     private _firstY: number = 0;
@@ -409,12 +404,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (this.onPointerDownHandler && this.onPointerDownHandler.script) {
-            this.onPointerDownHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-            return;
-        }
         // console.log(e.button)
         // console.log(e.nativeEvent)
         // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
@@ -464,11 +453,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerUp = (e: PointerEvent): void => {
-        if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-            this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            return;
-        }
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
-- 
cgit v1.2.3-70-g09d2


From 6e5abc3052f4df09b156deccdfe6a7b0b62f492b Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 20 Feb 2020 15:59:53 -0500
Subject: cleaned up a whole bunch of linking stuff.  link menu / link types /
 link default behaviors / added LinkBox

---
 src/client/documents/Documents.ts                  | 68 ++++++++++++----------
 src/client/util/LinkManager.ts                     | 48 +++------------
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/DocumentDecorations.tsx           | 13 +++--
 src/client/views/MainView.tsx                      |  2 +
 .../CollectionSchemaMovableTableHOC.tsx            |  7 ++-
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../views/collections/CollectionTreeView.tsx       | 24 ++++----
 src/client/views/linking/LinkEditor.tsx            | 20 +------
 src/client/views/linking/LinkMenuGroup.tsx         |  2 +-
 src/client/views/linking/LinkMenuItem.tsx          |  5 +-
 src/client/views/nodes/DocuLinkBox.tsx             | 23 +++++++-
 src/client/views/nodes/DocumentBox.tsx             |  6 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  3 +-
 src/client/views/nodes/DocumentView.tsx            |  9 ++-
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/LinkBox.scss                |  3 +
 src/client/views/nodes/LinkBox.tsx                 | 35 +++++++++++
 src/client/views/nodes/PresBox.tsx                 |  2 +-
 src/new_fields/ObjectField.ts                      |  1 -
 21 files changed, 152 insertions(+), 128 deletions(-)
 create mode 100644 src/client/views/nodes/LinkBox.scss
 create mode 100644 src/client/views/nodes/LinkBox.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 650538e93..2d2de54b7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -26,7 +26,6 @@ import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
-import { UndoManager, undoBatch } from "../util/UndoManager";
 import { YoutubeBox } from "../apis/youtube/YoutubeBox";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { LinkManager } from "../util/LinkManager";
@@ -56,6 +55,7 @@ import { extname } from "path";
 import { MessageStore } from "../../server/Message";
 import { ContextMenuProps } from "../views/ContextMenuItem";
 import { ContextMenu } from "../views/ContextMenu";
+import { LinkBox } from "../views/nodes/LinkBox";
 const requestImageSize = require('../util/request-image-size');
 const path = require('path');
 
@@ -85,6 +85,7 @@ export interface DocumentOptions {
     y?: number;
     z?: number;
     dropAction?: dropActionType;
+    chilDropAction?: dropActionType;
     layoutKey?: string;
     type?: string;
     title?: string;
@@ -103,6 +104,8 @@ export interface DocumentOptions {
     lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
     opacity?: number;
     defaultBackgroundColor?: string;
+    isBackground?: boolean;
+    isButton?: boolean;
     columnWidth?: number;
     fontSize?: number;
     curPage?: number;
@@ -220,6 +223,10 @@ export namespace Docs {
                 layout: { view: DirectoryImportBox, dataField: data },
                 options: { _height: 150 }
             }],
+            [DocumentType.LINK, {
+                layout: { view: LinkBox, dataField: data },
+                options: { _height: 75 }
+            }],
             [DocumentType.LINKDOC, {
                 data: new List<Doc>(),
                 layout: { view: EmptyBox, dataField: data },
@@ -399,8 +406,8 @@ export namespace Docs {
 
         Scripting.addGlobal(Buxton);
 
-        const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "_annotationOn",
-            "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover"];
+        const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
+            "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "isButton", "isBackground", "removeDropProperties"];
 
         /**
          * This function receives the relevant document prototype and uses
@@ -516,6 +523,30 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options);
         }
 
+        export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
+            const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isBackground: true, isButton: true, removeDropProperties: new List(["isBackground", "isButton"]), ...options });
+            const linkDocProto = Doc.GetProto(doc);
+            linkDocProto.anchor1 = source.doc;
+            linkDocProto.anchor2 = target.doc;
+            linkDocProto.anchor1Context = source.ctx;
+            linkDocProto.anchor2Context = target.ctx;
+            linkDocProto.anchor1Timecode = source.doc.currentTimecode;
+            linkDocProto.anchor2Timecode = target.doc.currentTimecode;
+
+            if (linkDocProto.layout_key1 === undefined) {
+                Cast(linkDocProto.proto, Doc, null)!.layout_key1 = DocuLinkBox.LayoutString("anchor1");
+                Cast(linkDocProto.proto, Doc, null)!.layout_key2 = DocuLinkBox.LayoutString("anchor2");
+                Cast(linkDocProto.proto, Doc, null)!.linkBoxExcludedKeys = new List(["treeViewExpandedView", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "proto", "aliasNumber", "title", "isPrototype", "lastOpened", "creationDate", "author"]);
+                Cast(linkDocProto.proto, Doc, null)!.layoutKey = undefined;
+            }
+
+            LinkManager.Instance.addLink(doc);
+
+            Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
+            Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
+            return doc;
+        }
+
         export function InkDocument(color: string, tool: number, strokeWidth: number, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
             const doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
             doc.color = color;
@@ -870,34 +901,11 @@ export namespace DocUtils {
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === CurrentUserUtils.UserDocument) return undefined;
 
-        const linkDocProto = new Doc(id, true);
-        UndoManager.RunInBatch(() => {
-            linkDocProto.type = DocumentType.LINK;
+        const linkDoc = Docs.Create.LinkDocument(source, target, { title }, id);
 
-            linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
-            linkDocProto.linkDescription = description;
-            linkDocProto.isPrototype = true;
-
-            linkDocProto.anchor1 = source.doc;
-            linkDocProto.anchor2 = target.doc;
-            linkDocProto.anchor1Context = source.ctx;
-            linkDocProto.anchor2Context = target.ctx;
-            linkDocProto.anchor1Groups = new List<Doc>([]);
-            linkDocProto.anchor2Groups = new List<Doc>([]);
-            linkDocProto.anchor1Timecode = source.doc.currentTimecode;
-            linkDocProto.anchor2Timecode = target.doc.currentTimecode;
-            linkDocProto.layout_key1 = DocuLinkBox.LayoutString("anchor1");
-            linkDocProto.layout_key2 = DocuLinkBox.LayoutString("anchor2");
-            linkDocProto.width = linkDocProto.height = 0;
-            linkDocProto.isBackground = true;
-            linkDocProto.isButton = true;
-
-            LinkManager.Instance.addLink(linkDocProto);
-
-            Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
-            Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
-        }, "make link");
-        return linkDocProto;
+        Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
+        Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
+        return linkDoc;
     }
 
     export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 1a793b524..1c328c04e 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -135,35 +135,13 @@ export class LinkManager {
             return DocListCast(linkDoc.anchor2Groups);
         }
     }
-
-    // sets the groups of the given anchor in the given link
-    public setAnchorGroups(linkDoc: Doc, anchor: Doc, groups: Doc[]) {
-        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
-            linkDoc.anchor1Groups = new List<Doc>(groups);
-        } else {
-            linkDoc.anchor2Groups = new List<Doc>(groups);
-        }
-    }
-
     public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) {
-        const groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
-        const index = groups.findIndex(gDoc => {
-            return StrCast(groupDoc.title).toUpperCase() === StrCast(gDoc.title).toUpperCase();
-        });
-        if (index > -1 && replace) {
-            groups[index] = groupDoc;
-        }
-        if (index === -1) {
-            groups.push(groupDoc);
-        }
-        LinkManager.Instance.setAnchorGroups(linkDoc, anchor, groups);
+        linkDoc.title = groupDoc.title;
     }
 
     // removes group doc of given group type only from given anchor on given link
     public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) {
-        const groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
-        const newGroups = groups.filter(groupDoc => StrCast(groupDoc.title).toUpperCase() !== groupType.toUpperCase());
-        LinkManager.Instance.setAnchorGroups(linkDoc, anchor, newGroups);
+        linkDoc.title = "-ungrouped-";
     }
 
     // returns map of group type to anchor's links in that group type
@@ -171,19 +149,10 @@ export class LinkManager {
         const related = this.getAllRelatedLinks(anchor);
         const anchorGroups = new Map<string, Array<Doc>>();
         related.forEach(link => {
-            const groups = LinkManager.Instance.getAnchorGroups(link, anchor);
-
-            if (groups.length > 0) {
-                groups.forEach(groupDoc => {
-                    const groupType = StrCast(groupDoc.title);
-                    if (groupType === "") {
-                        const group = anchorGroups.get("*");
-                        anchorGroups.set("*", group ? [...group, link] : [link]);
-                    } else {
-                        const group = anchorGroups.get(groupType);
-                        anchorGroups.set(groupType, group ? [...group, link] : [link]);
-                    }
-                });
+            if (link.title && link.title !== "-ungrouped-") {
+                const group = anchorGroups.get(StrCast(link.title));
+                anchorGroups.set(StrCast(link.title), group ? [...group, link] : [link]);
+
             } else {
                 // if link is in no groups then put it in default group
                 const group = anchorGroups.get("*");
@@ -215,10 +184,7 @@ export class LinkManager {
         const md: Doc[] = [];
         const allLinks = LinkManager.Instance.getAllLinks();
         allLinks.forEach(linkDoc => {
-            const anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, null));
-            const anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, null));
-            anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.title).toUpperCase() === groupType.toUpperCase()) { md.push(groupDoc); } });
-            anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.title).toUpperCase() === groupType.toUpperCase()) { md.push(groupDoc); } });
+            if (StrCast(linkDoc.title).toUpperCase() === groupType.toUpperCase()) { md.push(linkDoc); }
         });
         return md;
     }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c8ad78f47..b1cc8f26a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -120,7 +120,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                     const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
                     if (this.view0 && linkDoc) {
                         const proto = Doc.GetProto(linkDoc);
-                        proto.sourceContext = this.view0.props.ContainingCollectionDoc;
+                        proto.anchor1Context = this.view0.props.ContainingCollectionDoc;
 
                         const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
                         const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 07cc1d984..61c199c1d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,11 +77,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
             if (documentView.props.Document.type === DocumentType.LINK) {
-                const rect = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont")[0].getBoundingClientRect();
-                sptX = rect.left;
-                sptY = rect.top;
-                bptX = rect.right;
-                bptY = rect.bottom;
+                const docuBox = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont");
+                if (docuBox.length) {
+                    const rect = docuBox[0].getBoundingClientRect();
+                    sptX = rect.left;
+                    sptY = rect.top;
+                    bptX = rect.right;
+                    bptY = rect.bottom;
+                }
             }
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cc75a68fe..8697c601a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -278,6 +278,7 @@ export class MainView extends React.Component {
         if (this.darkScheme) {
             switch (doc.type) {
                 case DocumentType.TEXT || DocumentType.BUTTON: return "#2d2d2d";
+                case DocumentType.LINK:
                 case DocumentType.COL: {
                     if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
                 }
@@ -287,6 +288,7 @@ export class MainView extends React.Component {
             switch (doc.type) {
                 case DocumentType.TEXT: return "#f1efeb";
                 case DocumentType.BUTTON: return "lightgray";
+                case DocumentType.LINK:
                 case DocumentType.COL: {
                     if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray";
                 }
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 153bbd410..670d6dbb2 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -3,9 +3,9 @@ import { ReactTableDefaults, TableCellRenderer, RowInfo } from "react-table";
 import "./CollectionSchemaView.scss";
 import { Transform } from "../../util/Transform";
 import { Doc } from "../../../new_fields/Doc";
-import { DragManager, SetupDrag } from "../../util/DragManager";
+import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
-import { Cast, FieldValue } from "../../../new_fields/Types";
+import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { ContextMenu } from "../ContextMenu";
 import { action } from "mobx";
 import { library } from '@fortawesome/fontawesome-svg-core';
@@ -135,6 +135,7 @@ export interface MovableRowProps {
     rowFocused: boolean;
     textWrapRow: (doc: Doc) => void;
     rowWrapped: boolean;
+    dropAction: string;
 }
 
 export class MovableRow extends React.Component<MovableRowProps> {
@@ -219,7 +220,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
         if (!doc) return <></>;
 
         const reference = React.createRef<HTMLDivElement>();
-        const onItemDown = SetupDrag(reference, () => doc, this.move);
+        const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
 
         let className = "collectionSchema-row";
         if (this.props.rowFocused) className += " row-focused";
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 7dee7f18f..9486d195a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -408,7 +408,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             rowInfo,
             rowFocused: !this._headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document),
             textWrapRow: this.toggleTextWrapRow,
-            rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1
+            rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
+            dropAction: StrCast(this.props.Document.childDropAction)
         };
     }
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bebd99a3a..27a1c6367 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -41,6 +41,8 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
+    overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explict list (see LinkBox)
+    ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
     isAnnotationOverlay?: boolean;
     annotationsKey: string;
     layoutEngine?: () => string;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index dd622bf9d..15ad49e26 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -34,7 +34,6 @@ import "./CollectionTreeView.scss";
 import React = require("react");
 import { CollectionViewType } from './CollectionView';
 import { RichTextField } from '../../../new_fields/RichTextField';
-import { ObjectField } from '../../../new_fields/ObjectField';
 
 
 export interface TreeViewProps {
@@ -65,6 +64,7 @@ export interface TreeViewProps {
     treeViewPreventOpen: boolean;
     renderedIds: string[];
     onCheckedClick?: ScriptField;
+    ignoreFields?: string[];
 }
 
 library.add(faTrashAlt);
@@ -283,6 +283,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
         const rows: JSX.Element[] = [];
         for (const key of Object.keys(ids).slice().sort()) {
+            if (this.props.ignoreFields?.includes(key)) continue;
             const contents = doc[key];
             let contentElement: (JSX.Element | null)[] | JSX.Element = [];
 
@@ -293,11 +294,11 @@ class TreeView extends React.Component<TreeViewProps> {
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
                     this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
-                    [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
+                    [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.ignoreFields);
             } else {
                 contentElement = <EditableView
                     key="editableView"
-                    contents={contents !== undefined ? contents.toString() : "null"}
+                    contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
                     height={13}
                     fontSize={12}
                     GetValue={() => Field.toKeyValueString(doc, key)}
@@ -336,7 +337,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
                         this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
-                        [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
+                        [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.ignoreFields)}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
             return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -470,7 +471,8 @@ class TreeView extends React.Component<TreeViewProps> {
         treeViewPreventOpen: boolean,
         renderedIds: string[],
         libraryPath: Doc[] | undefined,
-        onCheckedClick: ScriptField | undefined
+        onCheckedClick: ScriptField | undefined,
+        ignoreFields: string[] | undefined
     ) {
         const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
@@ -579,7 +581,8 @@ class TreeView extends React.Component<TreeViewProps> {
                 active={active}
                 treeViewHideHeaderFields={treeViewHideHeaderFields}
                 treeViewPreventOpen={treeViewPreventOpen}
-                renderedIds={renderedIds} />;
+                renderedIds={renderedIds}
+                ignoreFields={ignoreFields} />;
         });
     }
 }
@@ -717,7 +720,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
         const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
-        return !this.childDocs ? (null) : (
+        const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
+        return !childDocs ? (null) : (
             <div className="collectionTreeView-dropTarget" id="body"
                 style={{ background: this.props.backgroundColor?.(this.props.Document), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
                 onContextMenu={this.onContextMenu}
@@ -736,15 +740,15 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         Doc.SetInPlace(this.dataDoc, "title", value, false);
                         const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         EditableView.loadId = doc[Id];
-                        this.addDoc(doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
+                        this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true);
                     })} />)}
                 {this.props.Document.allowClear ? this.renderClearButton : (null)}
                 <ul className="no-indent" style={{ width: "max-content" }} >
                     {
-                        TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
+                        TreeView.GetChildElements(childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
                             this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields),
-                            BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
+                            BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick), this.props.ignoreFields)
                     }
                 </ul>
             </div >
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 3a5cba81b..3c3767832 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -166,7 +166,7 @@ class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
     setMetadataValue = (value: string): void => {
         if (!this._keyError) {
             this._value = value;
-            this.props.mdDoc[this._key] = value;
+            Doc.GetProto(this.props.mdDoc)[this._key] = value;
         }
     }
 
@@ -343,25 +343,10 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         this.props.showLinks();
     }
 
-    @action
-    addGroup = (): void => {
-        // create new metadata document for group
-        // create new group document
-        const groupDoc = new Doc();
-        groupDoc.anchor1 = this.props.sourceDoc;
-        const opp = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
-        if (opp) {
-            groupDoc.anchor2 = opp;
-        }
-
-        LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, this.props.sourceDoc, groupDoc);
-    }
-
     render() {
         const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
 
-        const groupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc);
-        const groups = groupList.map(groupDoc => {
+        const groups = [this.props.linkDoc].map(groupDoc => {
             return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.title)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
         });
 
@@ -374,7 +359,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                 </div>
                 <div className="linkEditor-groupsLabel">
                     <b>Relationships:</b>
-                    <button className="linkEditor-button" onClick={() => this.addGroup()} title=" Add Group"><FontAwesomeIcon icon="plus" size="sm" /></button>
                 </div>
                 {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
             </div>
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 78c77e106..88f837a03 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -58,7 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
         if (index > -1) keys.splice(index, 1);
         const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
         const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
-        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table" }));
+        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table", childDropAction: "alias" }));
         const ref = React.createRef<HTMLDivElement>();
         return <div ref={ref}><button className="linkEditor-button linkEditor-tableButton" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
     }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 43a7daf00..7dd2c0fa8 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -39,9 +39,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     renderMetadata = (): JSX.Element => {
-        const groups = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc);
-        const index = groups.findIndex(groupDoc => StrCast(groupDoc.title).toUpperCase() === this.props.groupType.toUpperCase());
-        const groupDoc = index > -1 ? groups[index] : undefined;
+        const index = StrCast(this.props.linkDoc.title).toUpperCase() === this.props.groupType.toUpperCase() ? 0 : -1;
+        const groupDoc = index > -1 ? this.props.linkDoc : undefined;
 
         let mdRows: Array<JSX.Element> = [];
         if (groupDoc) {
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index a0b5cd8ec..1983ae529 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -1,6 +1,6 @@
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -11,6 +11,8 @@ import { DocComponent } from "../DocComponent";
 import "./DocuLinkBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
+import { ContextMenuProps } from "../ContextMenuItem";
+import { ContextMenu } from "../ContextMenu";
 
 type DocLinkSchema = makeInterface<[typeof documentSchema]>;
 const DocLinkDocument = makeInterface(documentSchema);
@@ -61,12 +63,27 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     onClick = (e: React.MouseEvent) => {
         if (!this.props.Document.onClick) {
             if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
-                DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, "inTab"), false);
+                if (this.props.Document.linkTarget === "doc") {
+                    const alias = Doc.MakeAlias(this.props.Document);
+                    alias.isButton = undefined;
+                    alias.isBackground = undefined;
+                    this.props.addDocTab(alias, StrCast(this.props.Document.linkOpenLocation, "inTab"));
+                } else {
+                    DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
+                }
             }
             e.stopPropagation();
         }
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({ description: "Open Target " + (this.props.Document.linkOpenLocation !== "onRight" ? "on Right" : "in Tab"), event: () => { e.stopPropagation(); this.props.Document.linkOpenLocation = this.props.Document.linkOpenLocation !== "onRight" ? "onRight" : "inTab" }, icon: "eye" });
+        funcs.push({ description: this.props.Document.linkTarget === "doc" ? "Open Link Target" : "Open Link Doc", event: () => { e.stopPropagation(); this.props.Document.linkTarget = this.props.Document.linkTarget === "doc" ? "anchor" : "doc" }, icon: "eye" });
+
+        ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
+    }
+
     render() {
         const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
         const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
@@ -76,7 +93,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
 
         const timecode = this.props.Document[anchor + "Timecode"];
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
-        return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle}
+        return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref} style={{
                 background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
                 transform: `scale(${anchorScale / this.props.ContentScaling()})`
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 6b7b652c6..8078d01ab 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -9,7 +9,7 @@ import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, emptyPath } from "../../../Utils";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocComponent } from "../DocComponent";
+import { DocComponent, DocAnnotatableComponent } from "../DocComponent";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import "./DocumentBox.scss";
@@ -20,7 +20,7 @@ type DocBoxSchema = makeInterface<[typeof documentSchema]>;
 const DocBoxDocument = makeInterface(documentSchema);
 
 @observer
-export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) {
+export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocumentBox, fieldKey); }
     _prevSelectionDisposer: IReactionDisposer | undefined;
     _selections: Doc[] = [];
@@ -80,7 +80,7 @@ export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocB
     pheight = () => this.props.PanelHeight() - 30;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-15, -15);
     render() {
-        const containedDoc = this.props.Document[this.props.fieldKey] as Doc;
+        const containedDoc = this.dataDoc[this.props.fieldKey] as Doc;
         return <div className="documentBox-container" ref={this._contRef}
             onContextMenu={this.specificContextMenu}
             onPointerDown={this.onPointerDown} onClick={this.onClick}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c0f603171..73a53f5cc 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
 import { AudioBox } from "./AudioBox";
 import { ButtonBox } from "./ButtonBox";
 import { SliderBox } from "./SliderBox";
+import { LinkBox } from "./LinkBox";
 import { DocumentBox } from "./DocumentBox";
 import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
@@ -108,7 +109,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                     FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
-                    ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox
+                    ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox
                 }}
                 bindings={this.CreateBindings()}
                 jsx={this.layout}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dea08600a..5a358b6d8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -198,7 +198,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             dragData.dropAction = dropAction;
             dragData.moveDocument = this.props.moveDocument;//  this.Document.onDragStart ? undefined : this.props.moveDocument;
             dragData.dragDivName = this.props.dragDivName;
-            this.props.Document.sourceContext = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically
+            this.props.Document.anchor1Context = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
         }
     }
@@ -534,7 +534,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
             de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
-                (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
+                (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "-ungrouped-")); // TODODO this is where in text links get passed
         }
     }
 
@@ -762,9 +762,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @computed get finalLayoutKey() {
-        const { layoutKey } = this.props;
-        if (typeof layoutKey === "string") {
-            return layoutKey;
+        if (typeof this.props.layoutKey === "string") {
+            return this.props.layoutKey;
         }
         const fallback = Cast(this.props.Document.layoutKey, "string");
         return typeof fallback === "string" ? fallback : "layout";
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 55d23da12..033511af4 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -114,7 +114,7 @@ export class FieldView extends React.Component<FieldViewProps> {
         //     return <WebBox {...this.props} />
         // }
         else if (!(field instanceof Promise)) {
-            return <p>{field.toString()}</p>;
+            return <p>{Field.toString(field)}</p>;
         }
         else {
             return <p> {"Waiting for server..."} </p>;
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
new file mode 100644
index 000000000..b5b8e660f
--- /dev/null
+++ b/src/client/views/nodes/LinkBox.scss
@@ -0,0 +1,3 @@
+.linkBox-container-interactive  {
+    pointer-events: all;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
new file mode 100644
index 000000000..0e327e130
--- /dev/null
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -0,0 +1,35 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface, listSpec } from "../../../new_fields/Schema";
+import { returnFalse, returnZero } from "../../../Utils";
+import { CollectionTreeView } from "../collections/CollectionTreeView";
+import { DocExtendableComponent } from "../DocComponent";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./LinkBox.scss";
+import { Cast } from "../../../new_fields/Types";
+
+type LinkDocument = makeInterface<[typeof documentSchema]>;
+const LinkDocument = makeInterface(documentSchema);
+
+@observer
+export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument>(LinkDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
+    render() {
+        return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`}
+            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
+            style={{ background: this.props.backgroundColor?.(this.props.Document) }} >
+
+            <CollectionTreeView {...this.props}
+                ChromeHeight={returnZero}
+                overrideDocuments={[this.dataDoc]}
+                ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)}
+                annotationsKey={""}
+                CollectionView={undefined}
+                addDocument={returnFalse}
+                removeDocument={returnFalse}
+                moveDocument={returnFalse}>
+            </CollectionTreeView>
+        </div>;
+    }
+} 
\ No newline at end of file
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 8d20bbe59..791ed5ef1 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -187,7 +187,7 @@ export class PresBox extends React.Component<FieldViewProps> {
 
         //docToJump stayed same meaning, it was not in the group or was the last element in the group
         const aliasOf = await Cast(docToJump.aliasOf, Doc);
-        const srcContext = aliasOf && await Cast(aliasOf.sourceContext, Doc);
+        const srcContext = aliasOf && await Cast(aliasOf.anchor1Context, Doc);
         if (docToJump === curDoc) {
             //checking if curDoc has navigation open
             const target = await Cast(curDoc.presentationTargetDoc, Doc);
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index 566104b40..9aa1c9b04 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -1,4 +1,3 @@
-import { Doc } from "./Doc";
 import { RefField } from "./RefField";
 import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols";
 import { Scripting } from "../client/util/Scripting";
-- 
cgit v1.2.3-70-g09d2


From 59e947ad4bd885c4bbf511a19eba64f672a3d76d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 22 Feb 2020 12:14:27 -0500
Subject: added option to update image native size

---
 src/client/views/collections/CollectionSubView.tsx |  4 ++--
 src/client/views/nodes/ImageBox.tsx                | 13 +++++++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 27a1c6367..6ad187e5c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -334,8 +334,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 const proto = Doc.GetProto(doc);
                 proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
                 if (Upload.isImageInformation(result)) {
-                    proto["data-nativeWidth"] = result.nativeWidth;
-                    proto["data-nativeHeight"] = result.nativeHeight;
+                    proto["data-nativeWidth"] = (result.nativeWidth > result.nativeHeight) ? 400 * result.nativeWidth / result.nativeHeight : 400;
+                    proto["data-nativeHeight"] = (result.nativeWidth > result.nativeHeight) ? 400 : 400 / (result.nativeWidth / result.nativeHeight);
                     proto.contentSize = result.contentSize;
                 }
                 generatedDocuments.push(doc);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bb421d315..99000a0a9 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -160,6 +160,19 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
+            funcs.push({
+                description: "Reset Native Dimensions", event: action(() => {
+                    const curNW = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]);
+                    const curNH = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]);
+                    if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
+                        this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
+                        this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
+                    } else {
+                        this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
+                        this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
+                    }
+                }), icon: "expand-arrows-alt"
+            });
 
             const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
             const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
-- 
cgit v1.2.3-70-g09d2


From dbdf81e191107240a62086ebe65d1dc5e3b503c6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 3 Mar 2020 00:18:36 -0500
Subject: several fixes to templates (simplified expanding, notes use 'text'
 field now, collections show documents when their data field is not a list). 
 multicol/row resizers select their doc.

---
 package-lock.json                                  | 95 +++++++++-------------
 src/client/documents/Documents.ts                  | 23 ++++--
 src/client/util/DropConverter.ts                   | 12 ++-
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  7 +-
 .../CollectionMulticolumnView.tsx                  |  9 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |  2 +
 src/client/views/nodes/DocumentContentsView.tsx    | 16 +---
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/new_fields/Doc.ts                              |  9 +-
 .../authentication/models/current_user_utils.ts    | 15 ++--
 11 files changed, 88 insertions(+), 106 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 827fb05b8..ef3ecc9f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -832,7 +832,7 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
                 "@types/express": "*"
@@ -2226,7 +2226,7 @@
                 },
                 "util": {
                     "version": "0.10.3",
-                    "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
+                    "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
                     "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
                     "dev": true,
                     "requires": {
@@ -2846,7 +2846,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2880,7 +2880,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -3051,7 +3051,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3844,7 +3844,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3856,7 +3856,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4398,7 +4398,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -5697,8 +5697,7 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5716,13 +5715,11 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5735,18 +5732,15 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5849,8 +5843,7 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5860,7 +5853,6 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5873,20 +5865,17 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "minipass": {
                     "version": "2.9.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5903,7 +5892,6 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5984,8 +5972,7 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5995,7 +5982,6 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -6071,8 +6057,7 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -6102,7 +6087,6 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -6120,7 +6104,6 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -6159,13 +6142,11 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "yallist": {
                     "version": "3.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 }
             }
         },
@@ -7383,7 +7364,7 @@
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7438,7 +7419,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -8169,7 +8150,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8506,7 +8487,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8538,7 +8519,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8713,7 +8694,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -9051,7 +9032,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -9134,7 +9115,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12785,7 +12766,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12798,7 +12779,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -13038,7 +13019,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -14464,7 +14445,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14906,7 +14887,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -15186,7 +15167,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -16048,7 +16029,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -16078,7 +16059,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -16094,7 +16075,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16911,7 +16892,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -18383,7 +18364,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index abef72f21..4df90ceb8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -94,6 +94,7 @@ export interface DocumentOptions {
     layoutKey?: string;
     type?: string;
     title?: string;
+    style?: string;
     page?: number;
     scale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
@@ -183,7 +184,7 @@ export namespace Docs {
 
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
-                layout: { view: FormattedTextBox, dataField: data },
+                layout: { view: FormattedTextBox, dataField: "text" },
                 options: { _height: 150, _xMargin: 10, _yMargin: 10 }
             }],
             [DocumentType.HIST, {
@@ -442,7 +443,7 @@ export namespace Docs {
          * only when creating a DockDocument from the current user's already existing
          * main document.
          */
-        export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string) {
+        export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") {
             const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
 
             if (!("author" in protoProps)) {
@@ -455,7 +456,7 @@ export namespace Docs {
 
             protoProps.isPrototype = true;
 
-            const dataDoc = MakeDataDelegate(proto, protoProps, data);
+            const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
             const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
 
             AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title));
@@ -473,10 +474,10 @@ export namespace Docs {
          * @param options initial values to apply to this new delegate
          * @param value the data to store in this new delegate
          */
-        function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D) {
+        function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data") {
             const deleg = Doc.MakeDelegate(proto);
             if (value !== undefined) {
-                deleg.data = value;
+                deleg[fieldKey] = value;
             }
             return Doc.assign(deleg, options);
         }
@@ -535,7 +536,7 @@ export namespace Docs {
         }
 
         export function TextDocument(text: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options);
+            return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options, undefined, "text");
         }
 
         export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
@@ -929,7 +930,15 @@ export namespace DocUtils {
             description: "Add Note ...",
             subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({
                 description: ":" + StrCast(note.title),
-                event: (args: { x: number, y: number }) => docTextAdder(Docs.Create.TextDocument("", { _width: 200, x, y, _autoHeight: note._autoHeight !== false, layout: note, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) })),
+                event: (args: { x: number, y: number }) => {
+                    const textDoc = Docs.Create.TextDocument("", {
+                        _width: 200, x, y, _autoHeight: note._autoHeight !== false,
+                        title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1)
+                    });
+                    textDoc.layoutKey = "layout_" + note.title;
+                    textDoc[textDoc.layoutKey] = note;
+                    docTextAdder(textDoc);
+                },
                 icon: "eye"
             })) as ContextMenuProps[],
             icon: "eye"
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 3c7caa60b..393e39687 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,5 +1,5 @@
 import { DragManager } from "./DragManager";
-import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../new_fields/Doc";
 import { DocumentType } from "../documents/DocumentTypes";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { StrCast } from "../../new_fields/Types";
@@ -8,7 +8,12 @@ import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { RichTextField } from "../../new_fields/RichTextField";
 import { ImageField } from "../../new_fields/URLField";
 
-export function makeTemplate(doc: Doc, first: boolean = true): boolean {
+// 
+// converts 'doc' into a template that can be used to render other documents.
+// the title of doc is used to determine which field is being templated, so
+// passing a value for 'rename' allows the doc to be given a meangingful name 
+// after it has been converted to
+export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean {
     const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
     const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0];
     const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, "");
@@ -29,6 +34,7 @@ export function makeTemplate(doc: Doc, first: boolean = true): boolean {
             any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc));
         }
     }
+    rename && (doc.title = rename);
     return any;
 }
 export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
@@ -38,7 +44,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
         if (!doc.onDragStart && !doc.onClick && !doc.isButtonBar) {
             const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
             if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) {
-                makeTemplate(layoutDoc);
+                !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
             } else {
                 (layoutDoc.layout instanceof Doc) && !data.userDropAction;
             }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index aa31d604e..527623ad4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -113,7 +113,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             return Cast(this.dataField, listSpec(Doc));
         }
         get childDocs() {
-            const docs = DocListCast(this.dataField);
+            const dfield = this.dataField;
+            const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate && !this.props.annotationsKey ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
+            const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
         }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d4f1a5444..af701347f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -109,12 +109,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
             this.props.addLiveTextDocument(
                 Docs.Create.TextDocument("", { _width: NumCast((FormattedTextBox.DefaultLayout as Doc)?._width) || 200, _height: 100, layout: FormattedTextBox.DefaultLayout, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
-        } else if (e.keyCode > 48 && e.keyCode <= 57) {
-            const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
-            const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
-            text.layout = notes[(e.keyCode - 49) % notes.length];
-            this.props.addLiveTextDocument(text);
-        }
+        } 
         e.stopPropagation();
     }
     //heuristically converts pasted text into a table.
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 82175c0b5..bd20781dc 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -223,17 +223,13 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
      */
     @computed
     private get contents(): JSX.Element[] | null {
-        // bcz: feels like a hack ... trying to show something useful when there's no list document in the data field of a templated object
-        const expanded = Cast(this.props.Document.expandedTemplate, Doc, null);
-        let { childLayoutPairs } = this.dataDoc[this.props.fieldKey] instanceof List || !expanded ? this : { childLayoutPairs: [] } as { childLayoutPairs: { layout: Doc, data: Doc }[] };
-        const replaced = !childLayoutPairs.length && !Cast(expanded?.layout, Doc, null) && expanded;
-        childLayoutPairs = childLayoutPairs.length || !replaced ? childLayoutPairs : [{ layout: replaced, data: replaced }];
+        let { childLayoutPairs } = this;
         const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
-            const width = () => expanded ? this.props.PanelWidth() : this.lookupPixels(layout);
+            const width = () => this.lookupPixels(layout);
             const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
                 <div className={"document-wrapper"}
@@ -248,6 +244,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
                 <ResizeBar
                     width={resizerWidth}
                     key={"resizer" + i}
+                    select={this.props.select}
                     columnUnitLength={this.getColumnUnitLength}
                     toLeft={layout}
                     toRight={childLayoutPairs[i + 1]?.layout}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 2cbeb3526..e1e604686 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -11,6 +11,7 @@ interface ResizerProps {
     columnUnitLength(): number | undefined;
     toLeft?: Doc;
     toRight?: Doc;
+    select: (isCtrlPressed: boolean) => void;
 }
 
 const resizerOpacity = 1;
@@ -23,6 +24,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
 
     @action
     private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+        this.props.select(false);
         e.stopPropagation();
         e.preventDefault();
         window.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 41478a3c5..dcb6d4a31 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -73,21 +73,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     get dataDoc() {
-        if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") {
-            // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), 
-            // then we render the layout document as a template and use this document as the data context for the template layout.
-            const proto = Doc.GetProto(this.props.Document);
-            return proto instanceof Promise ? undefined : proto;
-        }
-        return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc;
+        const proto = this.props.DataDoc || Doc.GetProto(this.props.Document);
+        return proto instanceof Promise ? undefined : proto;
     }
     get layoutDoc() {
-        if (this.props.LayoutDoc || (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string")) {
-            // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), 
-            // then we render the layout document as a template and use this document as the data context for the template layout.
-            return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document);
-        }
-        return Doc.Layout(this.props.Document);
+        return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document);
     }
 
     CreateBindings(): JsxBindings {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 41c94b923..7b434ebc1 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -207,7 +207,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
 
     updateTitle = () => {
-        if ((this.props.Document.isTemplateForField === "data" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
             const str = this._editorView.state.doc.textContent;
             const titlestr = str.substr(0, Math.min(40, str.length));
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index dcd97f079..0f3896055 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -449,12 +449,11 @@ export namespace Doc {
     }
 
     //
-    // Determines whether the combination of the layoutDoc and dataDoc represents
-    // a template relationship :  there is a dataDoc and it doesn't match the layoutDoc an
-    // the lyouatDoc's layout is layout string (not a document) 
+    // Determines whether the layout needs to be expanded (as a template).
+    // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template
     //
     export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
-        return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc && !(Doc.LayoutField(layoutDoc) instanceof Doc);
+        return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc;
     }
 
     //
@@ -610,7 +609,7 @@ export namespace Doc {
     export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
 
         // find the metadata field key that this template field doc will display (indicated by its title)
-        const metadataFieldKey = StrCast(templateField.title).replace(/^-/, "");
+        const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, "");
 
         // update the original template to mark it as a template
         templateField.isTemplateForField = metadataFieldKey;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index dc63f8a89..6216ab7e6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -43,15 +43,15 @@ export class CurrentUserUtils {
             Docs.Create.TextDocument("completed", { title: "completed", _backgroundColor: "green", color: "white" })
         ];
         const noteTemplates = [
-            Docs.Create.TextDocument("", { title: "Note", isTemplateDoc: true, backgroundColor: "yellow" }),
-            Docs.Create.TextDocument("", { title: "Idea", isTemplateDoc: true, backgroundColor: "pink" }),
-            Docs.Create.TextDocument("", { title: "Topic", isTemplateDoc: true, backgroundColor: "lightBlue" }),
-            Docs.Create.TextDocument("", { title: "Person", isTemplateDoc: true, backgroundColor: "lightGreen" }),
-            Docs.Create.TextDocument("", { title: "Todo", isTemplateDoc: true, backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption" })
+            Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Idea", isTemplateDoc: true, backgroundColor: "pink" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Topic", isTemplateDoc: true, backgroundColor: "lightBlue" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Person", isTemplateDoc: true, backgroundColor: "lightGreen" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Todo", isTemplateDoc: true, backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption" })
         ];
         doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" });
         Doc.enumeratedTextTemplate(Doc.GetProto(noteTemplates[4]), FormattedTextBox.LayoutString("Todo"), "taskStatus", taskStatusValues);
-        doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt) ? nt : nt), { title: "Note Types", _height: 75 }));
+        doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt, true, StrCast(nt.style)) ? nt : nt), { title: "Note Types", _height: 75 }));
     }
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
@@ -285,7 +285,8 @@ export class CurrentUserUtils {
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.descriptionBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: descriptionTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "sticky-note" });
-        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc, doc.descriptionBtn as Doc], {
+        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc, doc.descriptionBtn as Doc,
+        ...DocListCast(Cast(doc.noteTypes, Doc, null))], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
             backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true,
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
-- 
cgit v1.2.3-70-g09d2


From 636e6880a52d3a1a3e24437f47194a902731401d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Mar 2020 16:39:42 -0400
Subject: cleanup of warnings

---
 src/client/cognitive_services/CognitiveServices.ts   |  8 ++++----
 src/client/util/DocumentManager.ts                   |  2 +-
 src/client/util/DragManager.ts                       |  2 +-
 src/client/util/ProsemirrorExampleTransfer.ts        |  2 +-
 src/client/util/RichTextSchema.tsx                   | 14 +++++++-------
 src/client/util/SearchUtil.ts                        |  4 ++--
 src/client/views/KeyphraseQueryView.tsx              |  4 ++--
 .../views/collections/CollectionDockingView.tsx      |  4 ++--
 src/client/views/collections/CollectionSubView.tsx   |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx    |  2 +-
 .../CollectionMulticolumnView.tsx                    |  2 +-
 src/client/views/linking/LinkEditor.tsx              |  4 ++--
 src/client/views/nodes/AudioBox.tsx                  |  6 ++----
 src/client/views/nodes/DocuLinkBox.tsx               |  4 ++--
 src/client/views/nodes/DocumentView.tsx              | 14 +++++++-------
 src/client/views/nodes/FormattedTextBox.tsx          | 20 ++++++++------------
 src/client/views/nodes/PDFBox.tsx                    | 10 +++++-----
 17 files changed, 49 insertions(+), 55 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index ce829eb1e..542ccf04d 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -371,9 +371,9 @@ export namespace CognitiveServices {
                 let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
                 await requestPromise.post(args).then(async (wordvecs) => {
                     if (wordvecs) {
-                        let indices = Object.keys(wordvecs);
+                        const indices = Object.keys(wordvecs);
                         console.log("successful vectorization!");
-                        var vectorValues = new List<number>();
+                        const vectorValues = new List<number>();
                         indices.forEach((ind: any) => {
                             //console.log(wordvec.word);
                             vectorValues.push(wordvecs[ind]);
@@ -389,9 +389,9 @@ export namespace CognitiveServices {
             }
 
             export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, isInternal: boolean = true) => {
-                let results = await ExecuteQuery(Service.Text, Manager, data);
+                const results = await ExecuteQuery(Service.Text, Manager, data);
                 console.log("Cognitive Services keyphrases: ", results);
-                let { keyterms, external_recommendations, kp_string } = await converter(results, data);
+                const { keyterms, external_recommendations, kp_string } = await converter(results, data);
                 target[keys[0]] = keyterms;
                 if (isInternal) {
                     //await vectorize([data], dataDoc, isMainDoc);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 162a8fffe..c41304b9f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -209,7 +209,7 @@ export class DocumentManager {
             const maxLocation = StrCast(linkDoc.maximizeLocation, "inTab");
             const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
             const target = linkFollowDocs[reverse ? 1 : 0];
-            let annotatedDoc = await Cast(target.annotationOn, Doc);
+            const annotatedDoc = await Cast(target.annotationOn, Doc);
             if (annotatedDoc) {
                 annotatedDoc.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
             } else {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index dab5c842c..c11675894 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -198,7 +198,7 @@ export namespace DragManager {
             dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
             dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline"));
             return dropDoc;
-        }
+        };
         const finishDrag = (e: DragCompleteEvent) => {
             e.docDragData && (e.docDragData.droppedDocuments =
                 dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index ec5d1e72a..5cbf401d4 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -180,7 +180,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
             return true;
         }
         return false;
-    }
+    };
     bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
         return addTextOnRight(true);
     });
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 0adf060ec..31935df3e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -889,10 +889,10 @@ export class DashFieldView {
             e.stopPropagation();
             const collview = await Doc.addFieldEnumerations(self._textBoxDoc, node.attrs.fieldKey, [{ title: self._fieldSpan.innerText }]);
             collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
-        }
+        };
         const updateText = (forceMatch: boolean) => {
             self._enumerables.style.display = "none";
-            let newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
+            const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
 
             // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
             // holds the different enumerated values for the field in the titles of its collected documents.
@@ -909,12 +909,12 @@ export class DashFieldView {
 
                 // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
                 if (self._fieldSpan.innerText.startsWith(":=") && self._dashDoc) {
-                    self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
+                    self._dashDoc[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
                 } else if (self._fieldSpan.innerText.startsWith("=:=") && self._dashDoc) {
                     Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3));
                 }
             });
-        }
+        };
 
         this._fieldSpan = document.createElement("div");
         this._fieldSpan.id = Utils.GenerateGuid();
@@ -926,14 +926,14 @@ export class DashFieldView {
         this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
-        this._fieldSpan.onblur = function (e: any) { updateText(false); }
+        this._fieldSpan.onblur = function (e: any) { updateText(false); };
 
         const setDashDoc = (doc: Doc) => {
             self._dashDoc = doc;
             if (self._dashDoc && self._options?.length && !self._dashDoc[node.attrs.fieldKey]) {
                 self._dashDoc[node.attrs.fieldKey] = StrCast(self._options[0].title);
             }
-        }
+        };
         this._fieldSpan.onkeydown = function (e: any) {
             e.stopPropagation();
             if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
@@ -976,7 +976,7 @@ export class DashFieldView {
                 alias._pivotField = self._fieldKey;
                 tbox.props.addDocTab(alias, "onRight");
             }
-        }
+        };
         this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `;
         if (node.attrs.docid) {
             DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 64874b994..2d9c807dd 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -121,12 +121,12 @@ export namespace SearchUtil {
 
     export async function GetAllDocs() {
         const query = "*";
-        let response = await rp.get(Utils.prepend('/search'), {
+        const response = await rp.get(Utils.prepend('/search'), {
             qs:
                 { start: 0, rows: 10000, q: query },
 
         });
-        let result: IdSearchResult = JSON.parse(response);
+        const result: IdSearchResult = JSON.parse(response);
         const { ids, numFound, highlighting } = result;
         //console.log(ids.length);
         const docMap = await DocServer.GetRefFields(ids);
diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx
index a9dafc4a4..1dc156968 100644
--- a/src/client/views/KeyphraseQueryView.tsx
+++ b/src/client/views/KeyphraseQueryView.tsx
@@ -15,8 +15,8 @@ export class KeyphraseQueryView extends React.Component<KP_Props>{
     }
 
     render() {
-        let kps = this.props.keyphrases.toString();
-        let keyterms = this.props.keyphrases.split(',');
+        const kps = this.props.keyphrases.toString();
+        const keyterms = this.props.keyphrases.split(',');
         return (
             <div>
                 <h5>Select queries to send:</h5>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index db8f7d5e4..00e22d6fb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -138,8 +138,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 }
             }
             return false;
-        }
-        let retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? 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));
 
         retVal && instance.stateChanged();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 527623ad4..b995fc7d5 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -107,7 +107,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
             const { Document, DataDoc } = this.props;
             const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).filter(pair => pair.layout);
-            return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types
+            return validPairs.map(({ data, layout }) => ({ data, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
         get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 28f8bc048..7adafea0e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -801,7 +801,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this.props.Document.scrollY = NumCast(doc.y) - offset;
             }
 
-            afterFocus && setTimeout(() => afterFocus?.(), 1000);
+            afterFocus && setTimeout(afterFocus, 1000);
         } else {
             const layoutdoc = Doc.Layout(doc);
             const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index bd20781dc..aa8e1fb43 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -223,7 +223,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
      */
     @computed
     private get contents(): JSX.Element[] | null {
-        let { childLayoutPairs } = this;
+        const { childLayoutPairs } = this;
         const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ac4f8a3cf..b7f3dd995 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -253,8 +253,8 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
     render() {
         const groupType = StrCast(this.props.groupDoc.linkRelationship);
         // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
-        let buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
-        let addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>;
+        const buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
+        const addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>;
 
         return (
             <div className="linkEditor-group">
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 3c8b1c3b8..47883db4e 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -50,7 +50,6 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     _recordStart = 0;
     _stream: MediaStream | undefined;
 
-    public static START = 0;
     @observable private static _scrubTime = 0;
     @computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
     set audioState(value) { this.dataDoc.audioState = value; }
@@ -146,8 +145,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
         }).then(function (stream) {
             self._stream = stream;
             self._recorder = new MediaRecorder(stream);
-            self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date());
-            AudioBox.START = new DateField(new Date()).date.getTime();
+            self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date()); \
             AudioBox.ActiveRecordings.push(self.props.Document);
             self._recorder.ondataavailable = async function (e: any) {
                 const formData = new FormData();
@@ -208,7 +206,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
             _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height)
         });
         Doc.GetProto(newDoc).recordingSource = this.dataDoc;
-        Doc.GetProto(newDoc).recordingStart = 0;
+        Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`this.recordingSource[${this.props.fieldKey}+"-recordingStart"]`);
         Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("this.recordingSource.audioState");
         this.props.addDocument?.(newDoc);
         e.stopPropagation();
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index a0f5b3152..bc663d084 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -113,7 +113,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     openLinkEditor = action((e: React.MouseEvent) => {
         SelectionManager.DeselectAll();
         this._editing = this._forceOpen = true;
-    })
+    });
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
@@ -135,7 +135,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
         const flyout = (
             <div className="docuLinkBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
-                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)!} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
+                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
                 {!this._forceOpen ? (null) : <div className="docuLinkBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
                     <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
                 </div>}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 63c6b9f88..a1cba4c2e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -571,9 +571,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             else if (de.complete.docDragData.draggedDocuments[0].type === "text") {
                 const text = Cast(de.complete.docDragData.draggedDocuments[0].data, RichTextField)?.Text;
                 if (text && text[0] === "{" && text[text.length - 1] === "}" && text.includes(":")) {
-                    let loc = text.indexOf(":");
-                    let key = text.slice(1, loc);
-                    let value = text.slice(loc + 1, text.length - 1);
+                    const loc = text.indexOf(":");
+                    const key = text.slice(1, loc);
+                    const value = text.slice(loc + 1, text.length - 1);
                     console.log(key);
                     console.log(value);
                     console.log(this.props.Document);
@@ -756,7 +756,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             // a.download = `DocExport-${this.props.Document[Id]}.zip`;
             // a.click();
         });
-        let recommender_subitems: ContextMenuProps[] = [];
+        const recommender_subitems: ContextMenuProps[] = [];
 
         recommender_subitems.push({
             description: "Internal recommendations",
@@ -764,7 +764,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             icon: "brain"
         });
 
-        let ext_recommender_subitems: ContextMenuProps[] = [];
+        const ext_recommender_subitems: ContextMenuProps[] = [];
 
         ext_recommender_subitems.push({
             description: "arXiv",
@@ -872,7 +872,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
         }));
         const doclist = ClientRecommender.Instance.computeSimilarities("cosine");
-        let recDocs: { preview: Doc, score: number }[] = [];
+        const recDocs: { preview: Doc, score: number }[] = [];
         // tslint:disable-next-line: prefer-for-of
         for (let i = 0; i < doclist.length; i++) {
             recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
@@ -1113,7 +1113,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </> :
                 this.innards}
         </div>;
-        { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined }
+        { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
 }
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7f5f8538a..1fa603cbd 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -20,7 +20,7 @@ import { InkTool } from '../../../new_fields/InkField';
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
@@ -420,11 +420,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             //this._editorView!.focus();
         });
     }
-    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }
+    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
 
     @action
     toggleMenubar = () => {
-        this.props.Document._chromeStatus = this.props.Document._chromeStatus == "disabled" ? "enabled" : "disabled";
+        this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
     }
 
     recordBullet = async () => {
@@ -444,12 +444,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     setCurrentBulletContent = (value: string) => {
         if (this._editorView) {
-            let state = this._editorView.state;
-            let now = Date.now();
-            if (NumCast(this.props.Document.recordingStart, -1) === 0) {
-                this.props.Document.recordingStart = now = AudioBox.START;
-            }
-            console.log("NOW = " + (now - AudioBox.START) / 1000);
+            const state = this._editorView.state;
+            const now = Date.now();
             let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
             if (!this._break && state.selection.to !== state.selection.from) {
                 for (let i = state.selection.from; i <= state.selection.to; i++) {
@@ -461,9 +457,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     }
                 }
             }
+            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
             this._break = false;
-            console.log("start = " + (mark.attrs.modified * 1000 - AudioBox.START) / 1000);
-            value = "" + (mark.attrs.modified * 1000 - AudioBox.START) / 1000 + value;
+            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
             const from = state.selection.from;
             const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
             this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
@@ -867,7 +863,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (this._recording && !e.ctrlKey && e.button === 0) {
             this.stopDictation(true);
             this._break = true;
-            let state = this._editorView!.state;
+            const state = this._editorView!.state;
             const to = state.selection.to;
             const updated = TextSelection.create(state.doc, to, to);
             this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f47620c24..4076128b2 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -94,11 +94,11 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
     }
 
-    public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); }
-    public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }
-    public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }
-    public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
-    public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
+    public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); };
+    public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); };
+    public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); };
+    public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); };
+    public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); };
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
 
     @undoBatch
-- 
cgit v1.2.3-70-g09d2


From 1693e9daf1657c1fc45afa3fa14f0a0df0e01e13 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 17 Mar 2020 16:12:49 -0400
Subject: fixes for typing url in web box.  fixes for dropping imags from
 proxied document.  possible fix for updateing image width/height

---
 src/client/views/collections/CollectionSubView.tsx | 12 +++++---
 src/client/views/nodes/ImageBox.tsx                | 35 ++++++++++++----------
 src/client/views/nodes/WebBox.tsx                  |  6 ++--
 src/server/server_Initialization.ts                |  2 +-
 4 files changed, 31 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b995fc7d5..2d6777a4e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -234,7 +234,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (!html.startsWith("<a")) {
                     const tags = html.split("<");
                     if (tags[0] === "") tags.splice(0, 1);
-                    const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+                    let img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+                    const cors = img.includes("corsProxy") ? img.match(/http.*corsProxy\//)![0] : "";
+                    img = cors ? img.replace(cors, "") : img;
                     if (img) {
                         const split = img.split("src=\"")[1].split("\"")[0];
                         let source = split;
@@ -242,9 +244,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
                             source = Utils.prepend(accessPaths.agnostic.client);
                         }
-                        const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
-                        ImageUtils.ExtractExif(doc);
-                        addDocument(doc);
+                        if (source.startsWith("http")) {
+                            const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
+                            ImageUtils.ExtractExif(doc);
+                            addDocument(doc);
+                        }
                         return;
                     } else {
                         const path = window.location.origin + "/doc/";
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6ae32a1d6..f870a5df4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -258,22 +258,27 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             height: NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"])
         };
         const cachedImgPath = this.dataDoc[this.props.fieldKey + "-imgPath"];
+        const docAspect = this.Document[HeightSym]() / this.Document[WidthSym]();
+        const cachedAspect = cachedNativeSize.height / cachedNativeSize.width;
         if (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(NumCast(this.layoutDoc._width) / NumCast(this.layoutDoc._height) - cachedNativeSize.width / cachedNativeSize.height) > 0.05 || imgPath !== cachedImgPath) {
-            (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) && requestImageSize(imgPath).then((inquiredSize: any) => {
-                const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180;
-                const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize;
-                const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
-                const docAspect = this.Document[HeightSym]() / this.Document[WidthSym]();
-                setTimeout(action(() => {
-                    if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
-                        this.Document._height = this.Document[WidthSym]() * rotatedAspect;
-                        this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width;
-                        this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height;
-                    }
-                    this.dataDoc[this.props.fieldKey + "-imgPath"] = imgPath;
-                }), 0);
-            })
-                .catch((err: any) => console.log(err));
+            if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) {
+                requestImageSize(imgPath).then((inquiredSize: any) => {
+                    const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180;
+                    const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize;
+                    const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
+                    setTimeout(action(() => {
+                        if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
+                            this.Document._height = this.Document[WidthSym]() * rotatedAspect;
+                            this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width;
+                            this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height;
+                        }
+                        this.dataDoc[this.props.fieldKey + "-imgPath"] = imgPath;
+                    }), 0);
+                }).catch((err: any) => console.log(err));
+            } else if (Math.abs(1 - docAspect / cachedAspect) > 0.1) {
+                this.Document._width = this.Document[WidthSym]() || cachedNativeSize.width;
+                this.Document._height = this.Document[WidthSym]() * cachedAspect;
+            }
         } else if (this.Document._nativeWidth !== cachedNativeSize.width || this.Document._nativeHeight !== cachedNativeSize.height) {
             !(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc) && setTimeout(() => {
                 if (!(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc)) {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 24cb08c4c..557adfc03 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -81,14 +81,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
 
     @action
     submitURL = () => {
-        const script = KeyValueBox.CompileKVPScript(`new WebField("${this.url}")`);
-        if (!script) return;
-        KeyValueBox.ApplyKVPScript(this.props.Document, "data", script);
+        this.dataDoc[this.props.fieldKey] = new WebField(new URL(this.url));
     }
 
     @action
     setURL() {
-        const urlField: FieldResult<WebField> = Cast(this.props.Document.data, WebField);
+        const urlField: FieldResult<WebField> = Cast(this.dataDoc[this.props.fieldKey], WebField);
         if (urlField) this.url = urlField.url.toString();
         else this.url = "";
     }
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 76cb22521..7b2228831 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -129,7 +129,7 @@ function registerCorsProxy(server: express.Express) {
         // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, 
         // then we redirect again to the cors referer and just add the relative path.
         if (!requrl.startsWith("http") && req.originalUrl.startsWith("/corsProxy") && referer?.includes("corsProxy")) {
-            res.redirect(referer + requrl);
+            res.redirect(referer + (referer.endsWith("/") ? "" : "/") + requrl);
         }
         else {
             req.pipe(request(requrl)).on("response", res => {
-- 
cgit v1.2.3-70-g09d2


From 85a67ff30d86fa51b4534f717d3857aa23b732d1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 19 Mar 2020 12:04:32 -0400
Subject: added screen capture doc

---
 src/client/documents/DocumentTypes.ts              |   3 +-
 src/client/documents/Documents.ts                  |   9 +
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 src/client/views/nodes/ImageBox.tsx                |  27 ++-
 src/client/views/nodes/ScreenshotBox.scss          |  46 +++++
 src/client/views/nodes/ScreenshotBox.tsx           | 193 +++++++++++++++++++++
 src/client/views/nodes/VideoBox.tsx                |  17 +-
 src/client/views/nodes/WebBox.scss                 |   7 +-
 src/client/views/nodes/WebBox.tsx                  |   9 +-
 .../authentication/models/current_user_utils.ts    |   3 +-
 11 files changed, 296 insertions(+), 23 deletions(-)
 create mode 100644 src/client/views/nodes/ScreenshotBox.scss
 create mode 100644 src/client/views/nodes/ScreenshotBox.tsx

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index e3ce8e798..5ec1cfdb4 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -27,5 +27,6 @@ export enum DocumentType {
     DOCULINK = "doculink",
     PDFANNO = "pdfanno",
     INK = "ink",
-    DOCUMENT = "document"
+    DOCUMENT = "document",
+    SCREENSHOT = "screenshot",
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 131a48a2b..b5cffaddd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -58,6 +58,7 @@ import { MessageStore } from "../../server/Message";
 import { ContextMenuProps } from "../views/ContextMenuItem";
 import { ContextMenu } from "../views/ContextMenu";
 import { LinkBox } from "../views/nodes/LinkBox";
+import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
 const requestImageSize = require('../util/request-image-size');
 const path = require('path');
 
@@ -277,6 +278,10 @@ export namespace Docs {
             [DocumentType.INK, {
                 layout: { view: InkingStroke, dataField: data },
                 options: { backgroundColor: "transparent" }
+            }],
+            [DocumentType.SCREENSHOT, {
+                layout: { view: ScreenshotBox, dataField: data },
+                options: {}
             }]
         ]);
 
@@ -522,6 +527,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options);
         }
 
+        export function ScreenshotDocument(url: string, options: DocumentOptions = {}) {
+            return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options);
+        }
+
         export function AudioDocument(url: string, options: DocumentOptions = {}) {
             const instance = InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options);
             Doc.GetProto(instance).backgroundColor = ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'");
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2d6777a4e..97177855f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -316,7 +316,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 const item = e.dataTransfer.items[i];
                 if (item.kind === "string" && item.type.includes("uri")) {
                     const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
-                    const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+                    const type = "html";// (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
                     if (type) {
                         const doc = await Docs.Get.DocumentFromType(type, stringContents, options);
                         doc && generatedDocuments.push(doc);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f121ba3e0..239f414fd 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -29,6 +29,7 @@ import { ColorBox } from "./ColorBox";
 import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
 import { DocuLinkBox } from "./DocuLinkBox";
 import { PresElementBox } from "../presentationview/PresElementBox";
+import { ScreenshotBox } from "./ScreenshotBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
@@ -99,7 +100,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
                     ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox,
-                    RecommendationsBox,
+                    RecommendationsBox, ScreenshotBox
                 }}
                 bindings={this.CreateBindings()}
                 jsx={this.layout}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f870a5df4..b1c172e22 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,37 +1,34 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faFileAudio, faImage, faPaintBrush, faBrain } from '@fortawesome/free-solid-svg-icons';
+import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, trace } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, DataSym } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
+import { ObjectField } from '../../../new_fields/ObjectField';
 import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
 import { ComputedField } from '../../../new_fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { AudioField, ImageField } from '../../../new_fields/URLField';
-import { Utils, returnOne, emptyFunction } from '../../../Utils';
+import { TraceMobx } from '../../../new_fields/util';
+import { emptyFunction, returnOne, Utils } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
+import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
+import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../../views/ContextMenu";
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocAnnotatableComponent } from '../DocComponent';
 import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
-import { SearchUtil } from '../../util/SearchUtil';
-import { ClientRecommender } from '../../ClientRecommender';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { TraceMobx } from '../../../new_fields/util';
-import { SelectionManager } from '../../util/SelectionManager';
-import { cache } from 'sharp';
-import { ObjectField } from '../../../new_fields/ObjectField';
-import { Networking } from '../../Network';
 const requestImageSize = require('../../util/request-image-size');
 const path = require('path');
 const { Howl } = require('howler');
@@ -163,7 +160,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
             funcs.push({
-                description: "Reset Native Dimensions", event: action(() => {
+                description: "Reset Native Dimensions", event: action(async () => {
                     const curNW = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]);
                     const curNH = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]);
                     if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
new file mode 100644
index 000000000..4aaccb472
--- /dev/null
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -0,0 +1,46 @@
+.screenshotBox {
+    pointer-events: all;
+    transform-origin: top left;
+    background: white;
+    color: black;
+    // .screenshotBox-viewer {
+    //     opacity: 0.99;  // hack!  overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
+    // }
+    // .inkingCanvas-paths-markers {
+    //     opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
+    // }
+}
+
+.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen {
+    width: 100%;
+    z-index: -1; // 0;  // logically this should be 0 (or unset) which would give us transparent brush strokes over videos.  However, this makes Chrome crawl to a halt
+    position: absolute;
+}
+
+.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-content-fullScreen {
+    height: Auto; 
+}
+
+.screenshotBox-content-interactive, .screenshotBox-content-fullScreen  {
+    pointer-events: all;
+}
+
+.screenshotBox-snapshot{
+    color : white;
+    top :25px;
+    right : 25px;
+    position: absolute;
+    background-color:rgba(50, 50, 50, 0.2);
+    transform-origin: left top;
+    pointer-events:all;
+}
+
+.screenshotBox-recorder{
+    color : white;
+    top :25px;
+    right : 50px;
+    position: absolute;
+    background-color:rgba(50, 50, 50, 0.2);
+    transform-origin: left top;
+    pointer-events:all;
+}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
new file mode 100644
index 000000000..de9e521f6
--- /dev/null
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -0,0 +1,193 @@
+import React = require("react");
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faVideo } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as rp from 'request-promise';
+import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { DocAnnotatableComponent } from "../DocComponent";
+import { InkingControl } from "../InkingControl";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./ScreenshotBox.scss";
+const path = require('path');
+
+type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
+const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
+
+library.add(faVideo);
+
+@observer
+export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
+    private _reactionDisposer?: IReactionDisposer;
+    private _videoRef: HTMLVideoElement | null = null;
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
+
+    public get player(): HTMLVideoElement | null {
+        return this._videoRef;
+    }
+
+    videoLoad = () => {
+        const aspect = this.player!.videoWidth / this.player!.videoHeight;
+        const nativeWidth = (this.Document._nativeWidth || 0);
+        const nativeHeight = (this.Document._nativeHeight || 0);
+        if (!nativeWidth || !nativeHeight) {
+            if (!this.Document._nativeWidth) this.Document._nativeWidth = this.player!.videoWidth;
+            this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect;
+            this.Document._height = (this.Document._width || 0) / aspect;
+        }
+        if (!this.Document.duration) this.Document.duration = this.player!.duration;
+    }
+
+    @action public Snapshot() {
+        const width = this.Document._width || 0;
+        const height = this.Document._height || 0;
+        const canvas = document.createElement('canvas');
+        canvas.width = 640;
+        canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1);
+        const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+        if (ctx) {
+            ctx.rect(0, 0, canvas.width, canvas.height);
+            ctx.fillStyle = "blue";
+            ctx.fill();
+            this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
+        }
+
+        if (this._videoRef) {
+            //convert to desired file format
+            const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+            // if you want to preview the captured image,
+            const filename = path.basename(encodeURIComponent("screenshot" + Utils.GenerateGuid().replace(/\..*$/, "").replace(" ", "_")));
+            ScreenshotBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+                setTimeout(() => {
+                    if (returnedFilename) {
+                        const imageSummary = Docs.Create.ImageDocument(Utils.prepend(returnedFilename), {
+                            x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+                            _width: 150, _height: height / width * 150, title: "--screenshot--"
+                        });
+                        this.props.addDocument?.(imageSummary);
+                    }
+                }, 500);
+            });
+        }
+    }
+
+    componentDidMount() {
+    }
+
+    componentWillUnmount() {
+        this._reactionDisposer && this._reactionDisposer();
+    }
+
+    @action
+    setVideoRef = (vref: HTMLVideoElement | null) => {
+        this._videoRef = vref;
+    }
+
+    public static async convertDataUri(imageUri: string, returnedFilename: string) {
+        try {
+            const posting = Utils.prepend("/uploadURI");
+            const returnedUri = await rp.post(posting, {
+                body: {
+                    uri: imageUri,
+                    name: returnedFilename
+                },
+                json: true,
+            });
+            return returnedUri;
+
+        } catch (e) {
+            console.log(e);
+        }
+    }
+    @observable _screenCapture = false;
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+        if (field) {
+            const url = field.url.href;
+            const subitems: ContextMenuProps[] = [];
+            subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
+            subitems.push({
+                description: "Screen Capture", event: (async () => {
+                    runInAction(() => this._screenCapture = !this._screenCapture);
+                    this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+                }), icon: "expand-arrows-alt"
+            });
+            ContextMenu.Instance.addItem({ description: "Screenshot Funcs...", subitems: subitems, icon: "video" });
+        }
+    }
+
+    @computed get content() {
+        const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
+        const style = "videoBox-content" + interactive;
+        return <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+            style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
+            onCanPlay={this.videoLoad}
+            controls={true}
+            onClick={e => e.preventDefault()}>
+            <source type="video/mp4" />
+            Not supported.
+            </video>;
+    }
+
+    toggleRecording = action(async () => {
+        this._screenCapture = !this._screenCapture;
+        this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+    })
+
+    private get uIButtons() {
+        return ([
+            <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >
+                <FontAwesomeIcon icon="file" size="lg" />
+            </div>,
+            <div className="screenshotBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
+                <FontAwesomeIcon icon="camera" size="lg" />
+            </div>]);
+    }
+
+    onSnapshot = (e: React.PointerEvent) => {
+        this.Snapshot();
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+
+    contentFunc = () => [this.content];
+    render() {
+        return (<div className="videoBox" onContextMenu={this.specificContextMenu}
+            style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+            <div className="videoBox-viewer" >
+                <CollectionFreeFormView {...this.props}
+                    PanelHeight={this.props.PanelHeight}
+                    PanelWidth={this.props.PanelWidth}
+                    annotationsKey={this.annotationKey}
+                    focus={this.props.focus}
+                    isSelected={this.props.isSelected}
+                    isAnnotationOverlay={true}
+                    select={emptyFunction}
+                    active={this.annotationsActive}
+                    ContentScaling={returnOne}
+                    whenActiveChanged={this.whenActiveChanged}
+                    removeDocument={this.removeDocument}
+                    moveDocument={this.moveDocument}
+                    addDocument={returnFalse}
+                    CollectionView={undefined}
+                    ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                    renderDepth={this.props.renderDepth + 1}
+                    ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+                    {this.contentFunc}
+                </CollectionFreeFormView>
+            </div>
+            {this.uIButtons}
+        </div >);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 439f2d85f..24b66d8f7 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -195,6 +195,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
             console.log(e);
         }
     }
+    @observable _screenCapture = false;
     specificContextMenu = (e: React.MouseEvent): void => {
         const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
         if (field) {
@@ -203,6 +204,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
             subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
             subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
             subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
+            subitems.push({
+                description: "Screen Capture", event: (async () => {
+                    runInAction(() => this._screenCapture = !this._screenCapture);
+                    this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+                }), icon: "expand-arrows-alt"
+            });
             ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" });
         }
     }
@@ -212,8 +219,14 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
         const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
         const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
         return !field ? <div>Loading</div> :
-            <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
-                onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
+            <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+                style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
+                onCanPlay={this.videoLoad}
+                controls={VideoBox._showControls}
+                onPlay={() => this.Play()}
+                onSeeked={this.updateTimecode}
+                onPause={() => this.Pause()}
+                onClick={e => e.preventDefault()}>
                 <source src={field.url.href} type="video/mp4" />
                 Not supported.
             </video>;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 226103a53..b41687c11 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,13 +1,18 @@
 @import "../globalCssVariables.scss";
 
+
+.webBox-container, .webBox-container-dragging {
+    transform-origin: top left;
+}
 .webBox-cont,
-.webBox-cont-interactive {
+.webBox-cont-dragging {
     padding: 0vw;
     position: absolute;
     top: 0;
     left: 0;
     width: 100%;
     height: 100%;
+    transform-origin: top left;
     overflow: auto;
     pointer-events: none;
 }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 557adfc03..591864f2c 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -323,7 +323,14 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
             </>);
     }
     render() {
-        return (<div className={"webBox-container"} >
+        const dragging = "";//</div>!SelectionManager.GetIsDragging() ? "" : "-dragging";
+        return (<div className={`webBox-container${dragging}`}
+            style={{
+                transform: `scale(${this.props.ContentScaling()})`,
+                width: `${100 / this.props.ContentScaling()}%`,
+                height: `${100 / this.props.ContentScaling()}%`,
+                pointerEvents: this.props.Document.isBackground ? "none" : undefined
+            }} >
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index d1f68ba49..ea7b91b23 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -67,7 +67,8 @@ export class CurrentUserUtils {
             { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
             { title: "buxton", icon: "cloud-upload-alt", ignoreClick: true, drag: "Docs.Create.Buxton()" },
-            { title: "webcam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { width: 400, height: 400, title: "a test cam" })' },
+            { title: "screenshot", icon: "frame", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
+            { title: "webcam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
             { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
             { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
             { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },
-- 
cgit v1.2.3-70-g09d2


From b3f689c2ce60f92574430dad0448650ccf6b8d7e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 19 Mar 2020 19:18:55 -0400
Subject: moved facet collection stuff into CollectionView from
 CollectionTreeView

---
 src/client/views/collections/CollectionSubView.tsx |  40 +++-
 .../views/collections/CollectionTimeView.scss      |  51 -----
 .../views/collections/CollectionTimeView.tsx       | 244 +++++----------------
 src/client/views/collections/CollectionView.scss   |  54 +++++
 src/client/views/collections/CollectionView.tsx    | 175 ++++++++++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  40 +---
 6 files changed, 324 insertions(+), 280 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 97177855f..a5480d567 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -112,12 +112,48 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
-        get childDocs() {
+        @computed get childDocs() {
+            const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
+            const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+            const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
+            for (let i = 0; i < docFilters.length; i += 3) {
+                const [key, value, modifiers] = docFilters.slice(i, i + 3);
+                if (!filterFacets[key]) {
+                    filterFacets[key] = {};
+                }
+                filterFacets[key][value] = modifiers;
+            }
+
             const dfield = this.dataField;
             const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate && !this.props.annotationsKey ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
             const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
-            return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+            const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+
+            const filteredDocs = docFilters.length ? childDocs.filter(d => {
+                for (const facetKey of Object.keys(filterFacets)) {
+                    const facet = filterFacets[facetKey];
+                    const satisfiesFacet = Object.keys(facet).some(value =>
+                        (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
+                    if (!satisfiesFacet) {
+                        return false;
+                    }
+                }
+                return true;
+            }) : childDocs;
+            const rangeFilteredDocs = filteredDocs.filter(d => {
+                for (let i = 0; i < docRangeFilters.length; i += 3) {
+                    const key = docRangeFilters[i];
+                    const min = Number(docRangeFilters[i + 1]);
+                    const max = Number(docRangeFilters[i + 2]);
+                    const val = Cast(d[key], "number", null);
+                    if (val !== undefined && (val < min || val > max)) {
+                        return false;
+                    }
+                }
+                return true;
+            });
+            return rangeFilteredDocs;
         }
 
         @action
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index 310668ad2..be745000e 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -72,62 +72,11 @@
         }
     }
 
-    .collectionTimeView-treeView {
-        display: flex;
-        flex-direction: column;
-        width: 200px;
-        height: 100%;
-
-        .collectionTimeView-addfacet {
-            display: inline-block;
-            width: 200px;
-            height: 30px;
-            background: darkGray;
-            text-align: center;
-
-            .collectionTimeView-button {
-                align-items: center;
-                display: flex;
-                width: 100%;
-                height: 100%;
-
-                .collectionTimeView-span {
-                    margin: auto;
-                }
-            }
-
-            >div,
-            >div>div {
-                width: 100%;
-                height: 100%;
-                text-align: center;
-            }
-        }
-
-        .collectionTimeView-tree {
-            display: inline-block;
-            width: 100%;
-            height: calc(100% - 30px);
-        }
-    }
-
     .collectionTimeView-innards {
         display: inline-block;
         width: calc(100% - 200px);
         height: 100%;
     }
-
-    .collectionTimeView-dragger {
-        background-color: lightgray;
-        height: 40px;
-        width: 20px;
-        position: absolute;
-        border-radius: 10px;
-        top: 55%;
-        border: 1px black solid;
-        z-index: 2;
-        left: -10px;
-    }
 }
 
 .collectionTimeView-pivot {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 6d3b9b7f1..69922d6d9 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,16 +1,12 @@
-import { faEdit } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, trace } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Field, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
-import { listSpec } from "../../../new_fields/Schema";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
@@ -19,145 +15,33 @@ import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngi
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTimeView.scss";
-import { CollectionTreeView } from "./CollectionTreeView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { setupMoveUpEvents, returnFalse, emptyFunction } from "../../../Utils";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
     _changing = false;
     @observable _layoutEngine = "pivot";
-    @observable _facetWidth = 0;
     @observable _collapsed: boolean = false;
     componentWillUnmount() {
         this.props.Document.onChildClick = undefined;
     }
     componentDidMount() {
         this.props.Document._freezeOnDrop = true;
-        if (!this.props.Document._facetCollection) {
-            const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
-            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
-            facetCollection.target = this.props.Document;
-            facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
-            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]);
-
-            this.props.Document._facetCollection = facetCollection;
-            this.props.Document._fitToBox = true;
-        }
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']);  useRightSplit(alias, shiftKey); ";
         this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", containingCollection: Doc.name, shiftKey: "boolean" });
-
+        this.props.Document._fitToBox = true;
         if (!this.props.Document.onViewDefClick) {
             this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
         }
     }
 
-    bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
     layoutEngine = () => this._layoutEngine;
-    facetWidth = () => this._facetWidth;
     toggleVisibility = action(() => this._collapsed = !this._collapsed);
 
-    @computed get _allFacets() {
-        const facets = new Set<string>();
-        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
-        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
-        return Array.from(facets);
-    }
-
-    /**
-     * Responds to clicking the check box in the flyout menu
-     */
-    facetClick = (facetHeader: string) => {
-        const facetCollection = this.props.Document._facetCollection;
-        if (facetCollection instanceof Doc) {
-            const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
-            if (found !== -1) {
-                (facetCollection.data as List<Doc>).splice(found, 1);
-                const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
-                if (docFilter) {
-                    let index: number;
-                    while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
-                        docFilter.splice(index, 3);
-                    }
-                }
-                const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
-                if (docRangeFilters) {
-                    let index: number;
-                    while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
-                        docRangeFilters.splice(index, 3);
-                    }
-                }
-            } else {
-                const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
-                const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
-                    set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
-
-                let nonNumbers = 0;
-                let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
-                facetValues.map(val => {
-                    const num = Number(val);
-                    if (Number.isNaN(num)) {
-                        nonNumbers++;
-                    } else {
-                        minVal = Math.min(num, minVal);
-                        maxVal = Math.max(num, maxVal);
-                    }
-                });
-                if (nonNumbers / allCollectionDocs.length < .1) {
-                    const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
-                    const newFacet = Docs.Create.SliderDocument({ title: facetHeader });
-                    Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
-                    newFacet.treeViewExpandedView = "layout";
-                    newFacet.treeViewOpen = true;
-                    newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
-                    newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
-                    newFacet._sliderMinThumb = minVal;
-                    newFacet._sliderMaxThumb = maxVal;
-                    newFacet.target = this.props.Document;
-                    const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
-                    newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
-
-                    Doc.AddDocToList(facetCollection, "data", newFacet);
-                } else {
-                    const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
-                    const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
-                    const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
-                    newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
-                    Doc.AddDocToList(facetCollection, "data", newFacet);
-                }
-            }
-        }
-    }
-
-    menuCallback = (x: number, y: number) => {
-        ContextMenu.Instance.clearItems();
-        const docItems: ContextMenuProps[] = [];
-        const keySet: Set<string> = new Set();
-
-        this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
-            pair.layout[fieldKey] instanceof RichTextField ||
-            typeof (pair.layout[fieldKey]) === "number" ||
-            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
-        Array.from(keySet).map(fieldKey =>
-            docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
-        docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" });
-        ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
-        const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
-        ContextMenu.Instance.displayMenu(x, y, ":");
-    }
-
-    onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX,0)[0], 0);
-            return false;
-        }), returnFalse, () => this._facetWidth = this._facetWidth < 15 ? 200 : 0);
-    }
-
     onMinDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
             const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
@@ -188,34 +72,8 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     }
 
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: this.bodyPanelWidth() }}>
-            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
-        </div>;
-    }
-    @computed get filterView() {
-        trace();
-        const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
-        const flyout = (
-            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
-                {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
-                    <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
-                    <span className="checkmark" />
-                    {facet}
-                </label>)}
-            </div>
-        );
-        return <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
-            <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
-                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
-                    <div className="collectionTimeView-button">
-                        <span className="collectionTimeView-span">Facet Filters</span>
-                        <FontAwesomeIcon icon={faEdit} size={"lg"} />
-                    </div>
-                </Flyout>
-            </div>
-            <div className="collectionTimeView-tree" key="tree">
-                <CollectionTreeView {...this.props} PanelWidth={this.facetWidth} DataDoc={undefined} Document={facetCollection} />
-            </div>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
+            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
@@ -234,8 +92,30 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
         ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" });
     }
+    @computed get _allFacets() {
+        const facets = new Set<string>();
+        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+        return Array.from(facets);
+    }
+    menuCallback = (x: number, y: number) => {
+        ContextMenu.Instance.clearItems();
+        const docItems: ContextMenuProps[] = [];
+        const keySet: Set<string> = new Set();
 
-    render() {
+        this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
+            pair.layout[fieldKey] instanceof RichTextField ||
+            typeof (pair.layout[fieldKey]) === "number" ||
+            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
+        Array.from(keySet).map(fieldKey =>
+            docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
+        docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" });
+        ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
+        const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        ContextMenu.Instance.displayMenu(x, y, ":");
+    }
+
+    @computed get pivotKeyUI() {
         const newEditableViewProps = {
             GetValue: () => "",
             SetValue: (value: any) => {
@@ -250,7 +130,26 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             toggle: this.toggleVisibility,
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
+        return <div className={"pivotKeyEntry"}>
+            <button className="collectionTimeView-backBtn"
+                onClick={action(() => {
+                    let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+                    if (prevFilterIndex > 0) {
+                        prevFilterIndex--;
+                        this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+                        this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+                        this.props.Document._prevFilterIndex = prevFilterIndex;
+                    } else {
+                        this.props.Document._docFilters = new List([]);
+                    }
+                })}>
+                back
+            </button>
+            <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
+        </div>
+    }
 
+    render() {
         let nonNumbers = 0;
         this.childDocs.map(doc => {
             const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)])));
@@ -270,41 +169,16 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             }
         }
 
-
-        const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
-        return !facetCollection ? (null) :
-            <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
-                style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
-                <div className={"pivotKeyEntry"}>
-                    <button className="collectionTimeView-backBtn"
-                        onClick={action(() => {
-                            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-                            if (prevFilterIndex > 0) {
-                                prevFilterIndex--;
-                                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                                this.props.Document._prevFilterIndex = prevFilterIndex;
-                            } else {
-                                this.props.Document._docFilters = new List([]);
-                            }
-                        })}>
-                        back
-                    </button>
-                    <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
-                </div>
-                {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) :
-                    <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
-                        <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
-                    </div>
-                }
-                {this.filterView}
-                {this.contents}
-                {!this.props.isSelected() || !doTimeline ? (null) : <>
-                    <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
-                    <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
-                    <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
-                </>}
-            </div>;
+        return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
+            style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            {this.pivotKeyUI}
+            {this.contents}
+            {!this.props.isSelected() || !doTimeline ? (null) : <>
+                <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
+                <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
+                <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
+            </>}
+        </div>;
     }
 }
 
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index 1c46081a1..0246abccc 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -10,6 +10,60 @@
     width: 100%;
     height: 100%;
     overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
+
+
+    .collectionTimeView-dragger {
+        background-color: lightgray;
+        height: 40px;
+        width: 20px;
+        position: absolute;
+        border-radius: 10px;
+        top: 55%;
+        border: 1px black solid;
+        z-index: 2;
+        left: -10px;
+    }
+    .collectionTimeView-treeView {
+        display: flex;
+        flex-direction: column;
+        width: 200px;
+        height: 100%;
+        position: absolute;
+        left: 0;
+        top: 0;
+
+        .collectionTimeView-addfacet {
+            display: inline-block;
+            width: 200px;
+            height: 30px;
+            background: darkGray;
+            text-align: left;
+
+            .collectionTimeView-button {
+                align-items: center;
+                display: flex;
+                width: 100%;
+                height: 100%;
+
+                .collectionTimeView-span {
+                    margin: auto;
+                }
+            }
+
+            >div,
+            >div>div {
+                width: 100%;
+                height: 100%;
+                text-align: center;
+            }
+        }
+
+        .collectionTimeView-tree {
+            display: inline-block;
+            width: 100%;
+            height: calc(100% - 30px);
+        }
+    }
 }
 
 #google-tags {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7683e1550..a1f173746 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,18 +1,19 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
+import { faEye, faEdit } from '@fortawesome/free-regular-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
-import { action, observable } from 'mobx';
+import { action, observable, computed } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, Field } from '../../../new_fields/Doc';
 import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -38,6 +39,12 @@ import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { Id } from '../../../new_fields/FieldSymbols';
+import { listSpec } from '../../../new_fields/Schema';
+import { Docs } from '../../documents/Documents';
+import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -84,6 +91,7 @@ export interface CollectionRenderProps {
     moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
+    PanelWidth: () => number;
 }
 
 @observer
@@ -268,6 +276,158 @@ export class CollectionView extends Touchable<FieldViewProps> {
             onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
             onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
     }
+    @observable _facetWidth = 0;
+
+    bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
+    getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
+    facetWidth = () => this._facetWidth;
+
+    @computed get dataDoc() {
+        return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
+            this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+    }
+    // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
+    // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
+    // to its children which may be templates.
+    // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
+    @computed get dataField() {
+        return this.dataDoc[this.props.fieldKey];
+    }
+
+    get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
+        const { Document, DataDoc } = this.props;
+        const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout);
+        return validPairs.map(({ data, layout }) => ({ data, layout: layout! })); // this mapping is a bit of a hack to coerce types
+    }
+    get childDocList() {
+        return Cast(this.dataField, listSpec(Doc));
+    }
+    get childDocs() {
+        const dfield = this.dataField;
+        const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
+        const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
+        const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+        return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+    }
+    @computed get _allFacets() {
+        const facets = new Set<string>();
+        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+        return Array.from(facets);
+    }
+
+    /**
+     * Responds to clicking the check box in the flyout menu
+     */
+    facetClick = (facetHeader: string) => {
+        const facetCollection = this.props.Document._facetCollection;
+        if (facetCollection instanceof Doc) {
+            const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
+            if (found !== -1) {
+                (facetCollection.data as List<Doc>).splice(found, 1);
+                const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
+                if (docFilter) {
+                    let index: number;
+                    while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+                        docFilter.splice(index, 3);
+                    }
+                }
+                const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
+                if (docRangeFilters) {
+                    let index: number;
+                    while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
+                        docRangeFilters.splice(index, 3);
+                    }
+                }
+            } else {
+                const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+                const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
+                    set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+
+                let nonNumbers = 0;
+                let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+                facetValues.map(val => {
+                    const num = Number(val);
+                    if (Number.isNaN(num)) {
+                        nonNumbers++;
+                    } else {
+                        minVal = Math.min(num, minVal);
+                        maxVal = Math.max(num, maxVal);
+                    }
+                });
+                if (nonNumbers / allCollectionDocs.length < .1) {
+                    const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
+                    const newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+                    Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+                    newFacet.treeViewExpandedView = "layout";
+                    newFacet.treeViewOpen = true;
+                    newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
+                    newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
+                    newFacet._sliderMinThumb = minVal;
+                    newFacet._sliderMaxThumb = maxVal;
+                    newFacet.target = this.props.Document;
+                    const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
+                    newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+
+                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                } else {
+                    const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+                    const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+                    const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+                    newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                }
+            }
+        }
+    }
+
+
+    onPointerDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+            return false;
+        }), returnFalse, action(() => this._facetWidth = this._facetWidth < 15 ? 200 : 0));
+    }
+    @computed get filterView() {
+        const facetCollection = Cast(this.props.Document?._facetCollection, Doc);
+        if (this._facetWidth && facetCollection === undefined) setTimeout(() => {
+            const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
+            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
+            facetCollection.target = this.props.Document;
+            facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]);
+
+            this.props.Document._facetCollection = facetCollection;
+        }, 0);
+        const flyout = (
+            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
+                {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+                    <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
+                    <span className="checkmark" />
+                    {facet}
+                </label>)}
+            </div>
+        );
+        return !facetCollection ? (null) :
+            <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+                <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+                    <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+                        <div className="collectionTimeView-button">
+                            <span className="collectionTimeView-span">Facet Filters</span>
+                            <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                        </div>
+                    </Flyout>
+                </div>
+                <div className="collectionTimeView-tree" key="tree">
+                    <CollectionTreeView {...this.props} CollectionView={this} annotationsKey={""} PanelWidth={this.facetWidth}
+                        DataDoc={undefined} Document={facetCollection}
+                        moveDocument={(doc: Doc) => false}
+                        removeDocument={(doc: Doc) => false}
+                        addDocument={(doc: Doc) => false} />
+                </div>
+            </div>;
+    }
+
     render() {
         TraceMobx();
         const props: CollectionRenderProps = {
@@ -276,6 +436,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             moveDocument: this.moveDocument,
             active: this.active,
             whenActiveChanged: this.whenActiveChanged,
+            PanelWidth: this.bodyPanelWidth
         };
         return (<div className={"collectionView"}
             style={{
@@ -292,6 +453,12 @@ export class CollectionView extends Touchable<FieldViewProps> {
                         Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
                     :
                     ""))}
+            {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) :
+                <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+                    <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
+                </div>
+            }
+            {this.filterView}
         </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index da44d0d59..a0dd4f2de 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -903,12 +903,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }.bind(this));
 
     doTimelineLayout(poolData: Map<string, PoolData>) {
-        return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
+        return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
     doPivotLayout(poolData: Map<string, PoolData>) {
-        return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
+        return computePivotLayout(poolData, this.props.Document, this.childDocs, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
@@ -934,42 +934,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
 
-    @computed get filterDocs() {
-        const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
-        const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
-        const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
-        for (let i = 0; i < docFilters.length; i += 3) {
-            const [key, value, modifiers] = docFilters.slice(i, i + 3);
-            if (!filterFacets[key]) {
-                filterFacets[key] = {};
-            }
-            filterFacets[key][value] = modifiers;
-        }
-        const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
-            for (const facetKey of Object.keys(filterFacets)) {
-                const facet = filterFacets[facetKey];
-                const satisfiesFacet = Object.keys(facet).some(value =>
-                    (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
-                if (!satisfiesFacet) {
-                    return false;
-                }
-            }
-            return true;
-        }) : this.childDocs;
-        const rangeFilteredDocs = filteredDocs.filter(d => {
-            for (let i = 0; i < docRangeFilters.length; i += 3) {
-                const key = docRangeFilters[i];
-                const min = Number(docRangeFilters[i + 1]);
-                const max = Number(docRangeFilters[i + 2]);
-                const val = Cast(d[key], "number", null);
-                if (val !== undefined && (val < min || val > max)) {
-                    return false;
-                }
-            }
-            return true;
-        });
-        return rangeFilteredDocs;
-    }
     childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
-- 
cgit v1.2.3-70-g09d2


From a75ce06979b17457e6ccbdda8fb2217815757036 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 19 Mar 2020 21:31:45 -0400
Subject: cleaned up facet collections to not require an additional document

---
 src/Utils.ts                                       |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   8 +-
 .../views/collections/CollectionTimeView.scss      |   1 +
 .../views/collections/CollectionTreeView.tsx       |  24 ++--
 src/client/views/collections/CollectionView.tsx    | 143 +++++++++++----------
 src/client/views/nodes/FieldView.tsx               |   3 +-
 6 files changed, 99 insertions(+), 82 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 3786b4f6f..e3ec10dcd 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -498,7 +498,7 @@ export function setupMoveUpEvents(
     };
     const _upEvent = (e: PointerEvent): void => {
         upEvent(e);
-        if (Math.abs(e.clientX - (target as any)._downX) < 4 || Math.abs(e.clientY - (target as any)._downY) < 4) {
+        if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) {
             clickEvent(e);
         }
         document.removeEventListener("pointermove", _moveEvent);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a5480d567..11f214625 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -48,8 +48,8 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     layoutEngine?: () => string;
 }
 
-export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
-    class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
+export function CollectionSubView<T,X>(schemaCtor: (doc: Doc) => T, moreProps?:X) {
+    class CollectionSubView extends DocComponent<X&SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -130,7 +130,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
 
-            const filteredDocs = docFilters.length ? childDocs.filter(d => {
+            const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => {
                 for (const facetKey of Object.keys(filterFacets)) {
                     const facet = filterFacets[facetKey];
                     const satisfiesFacet = Object.keys(facet).some(value =>
@@ -195,7 +195,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @undoBatch
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
-            const docDragData = de.complete.docDragData;
+        const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
             if (docDragData) {
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index be745000e..fa7c87f4e 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -48,6 +48,7 @@
     .collectionTimeView-flyout {
         width: 400px;
         display: block;
+        text-align: left;
 
         .collectionTimeView-flyout-item {
             background-color: lightgray;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f8f8a0943..c02c4ac3f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -6,7 +6,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, Field, HeightSym, WidthSym, DataSym, Opt } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
-import { Document, listSpec } from '../../../new_fields/Schema';
+import { Document, listSpec, createSchema, makeInterface } from '../../../new_fields/Schema';
 import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
@@ -29,7 +29,7 @@ import { ImageBox } from '../nodes/ImageBox';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { ScriptBox } from '../ScriptBox';
 import { Templates } from '../Templates';
-import { CollectionSubView } from "./CollectionSubView";
+import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
 import { CollectionViewType } from './CollectionView';
@@ -46,7 +46,7 @@ export interface TreeViewProps {
     renderDepth: number;
     deleteDoc: (doc: Doc) => boolean;
     moveDocument: DragManager.MoveFunction;
-    dropAction: "alias" | "copy" | undefined;
+    dropAction: dropActionType;
     addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
     pinToPres: (document: Doc) => void;
     panelWidth: () => number;
@@ -632,8 +632,16 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 }
 
+export type collectionTreeViewProps = {
+    treeViewHideTitle?: boolean;
+    treeViewHideHeaderFields?: boolean;
+    onCheckedClick?: ScriptField;
+};
+
+let xx: collectionTreeViewProps = {};
+
 @observer
-export class CollectionTreeView extends CollectionSubView(Document) {
+export class CollectionTreeView extends CollectionSubView(Document, xx) {
     private treedropDisposer?: DragManager.DragDropDisposer;
     private _mainEle?: HTMLDivElement;
 
@@ -665,7 +673,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         const doAddDoc = () =>
             Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false);
         if (this.props.Document.resolvedDataDoc instanceof Promise) {
-            this.props.Document.resolvedDataDoc.then(resolved => doAddDoc());
+            this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc());
         } else {
             doAddDoc();
         }
@@ -773,7 +781,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                 onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
                 onDrop={this.onTreeDrop}
                 ref={this.createTreeDropTarget}>
-                {(this.props.Document.treeViewHideTitle ? (null) : <EditableView
+                {(this.props.treeViewHideTitle || this.props.Document.treeViewHideTitle ? (null) : <EditableView
                     contents={this.dataDoc.title}
                     editing={false}
                     display={"block"}
@@ -792,8 +800,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     {
                         TreeView.GetChildElements(childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
-                            this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields),
-                            BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick), this.props.ignoreFields)
+                            this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.props.Document.treeViewHideHeaderFields),
+                            BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick || ScriptCast(this.props.Document.onCheckedClick), this.props.ignoreFields)
                     }
                 </ul>
             </div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a1f173746..ab553b206 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast, Field } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, Field, Opt } from '../../../new_fields/Doc';
 import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
@@ -320,64 +320,63 @@ export class CollectionView extends Touchable<FieldViewProps> {
      * Responds to clicking the check box in the flyout menu
      */
     facetClick = (facetHeader: string) => {
-        const facetCollection = this.props.Document._facetCollection;
-        if (facetCollection instanceof Doc) {
-            const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
-            if (found !== -1) {
-                (facetCollection.data as List<Doc>).splice(found, 1);
-                const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
-                if (docFilter) {
-                    let index: number;
-                    while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
-                        docFilter.splice(index, 3);
-                    }
+        const facetCollection = this.props.Document;
+        const found = DocListCast(facetCollection[this.props.fieldKey + "-filter"]).findIndex(doc => doc.title === facetHeader);
+        if (found !== -1) {
+            (facetCollection[this.props.fieldKey + "-filter"] as List<Doc>).splice(found, 1);
+            const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
+            if (docFilter) {
+                let index: number;
+                while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+                    docFilter.splice(index, 3);
                 }
-                const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
-                if (docRangeFilters) {
-                    let index: number;
-                    while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
-                        docRangeFilters.splice(index, 3);
-                    }
+            }
+            const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
+            if (docRangeFilters) {
+                let index: number;
+                while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
+                    docRangeFilters.splice(index, 3);
                 }
-            } else {
-                const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
-                const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
-                    set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
-
-                let nonNumbers = 0;
-                let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
-                facetValues.map(val => {
-                    const num = Number(val);
-                    if (Number.isNaN(num)) {
-                        nonNumbers++;
-                    } else {
-                        minVal = Math.min(num, minVal);
-                        maxVal = Math.max(num, maxVal);
-                    }
-                });
-                if (nonNumbers / allCollectionDocs.length < .1) {
-                    const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
-                    const newFacet = Docs.Create.SliderDocument({ title: facetHeader });
-                    Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
-                    newFacet.treeViewExpandedView = "layout";
-                    newFacet.treeViewOpen = true;
-                    newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
-                    newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
-                    newFacet._sliderMinThumb = minVal;
-                    newFacet._sliderMaxThumb = maxVal;
-                    newFacet.target = this.props.Document;
-                    const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
-                    newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
-
-                    Doc.AddDocToList(facetCollection, "data", newFacet);
+            }
+        } else {
+            const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+            const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
+                set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+
+            let nonNumbers = 0;
+            let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+            facetValues.map(val => {
+                const num = Number(val);
+                if (Number.isNaN(num)) {
+                    nonNumbers++;
                 } else {
-                    const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
-                    const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
-                    const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
-                    newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
-                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                    minVal = Math.min(num, minVal);
+                    maxVal = Math.max(num, maxVal);
                 }
+            });
+            let newFacet: Opt<Doc>;
+            if (nonNumbers / allCollectionDocs.length < .1) {
+                newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+                const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
+                Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+                newFacet.treeViewExpandedView = "layout";
+                newFacet.treeViewOpen = true;
+                newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
+                newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
+                newFacet._sliderMinThumb = minVal;
+                newFacet._sliderMaxThumb = maxVal;
+                newFacet.target = this.props.Document;
+                const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
+                newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+
+                Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
+            } else {
+                newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+                const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+                const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+                newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
             }
+            Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
         }
     }
 
@@ -388,27 +387,24 @@ export class CollectionView extends Touchable<FieldViewProps> {
             return false;
         }), returnFalse, action(() => this._facetWidth = this._facetWidth < 15 ? 200 : 0));
     }
+    filterBackground = () => "dimGray";
+    @computed get scriptField() {
+        const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
+        return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+    }
     @computed get filterView() {
-        const facetCollection = Cast(this.props.Document?._facetCollection, Doc);
-        if (this._facetWidth && facetCollection === undefined) setTimeout(() => {
-            const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
-            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
-            facetCollection.target = this.props.Document;
-            facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
-            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]);
-
-            this.props.Document._facetCollection = facetCollection;
-        }, 0);
+        const facetCollection = this.props.Document;
+        this._facetWidth && setTimeout(() => facetCollection.target = this.props.Document, 0);
         const flyout = (
             <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
                 {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
-                    <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
+                    <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey + "-filter"]).some(d => d.title === facet)} />
                     <span className="checkmark" />
                     {facet}
                 </label>)}
             </div>
         );
-        return !facetCollection ? (null) :
+        return !this._facetWidth || this.props.dontRegisterView ? (null) :
             <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
                 <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
                     <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
@@ -419,8 +415,19 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     </Flyout>
                 </div>
                 <div className="collectionTimeView-tree" key="tree">
-                    <CollectionTreeView {...this.props} CollectionView={this} annotationsKey={""} PanelWidth={this.facetWidth}
-                        DataDoc={undefined} Document={facetCollection}
+                    <CollectionTreeView {...this.props}
+                        CollectionView={this}
+                        treeViewHideTitle={true}
+                        treeViewHideHeaderFields={true}
+                        onCheckedClick={this.scriptField!}
+                        ignoreFields={["_facetCollection", "_docFilters"]}
+                        annotationsKey={""}
+                        dontRegisterView={true}
+                        PanelWidth={this.facetWidth}
+                        DataDoc={facetCollection}
+                        Document={facetCollection}
+                        backgroundColor={this.filterBackground}
+                        fieldKey={`${this.props.fieldKey}-filter`}
                         moveDocument={(doc: Doc) => false}
                         removeDocument={(doc: Doc) => false}
                         addDocument={(doc: Doc) => false} />
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 6619330a1..0305f43d5 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -10,6 +10,7 @@ import { Transform } from "../../util/Transform";
 import { CollectionView } from "../collections/CollectionView";
 import { AudioBox } from "./AudioBox";
 import { VideoBox } from "./VideoBox";
+import { dropActionType } from "../../util/DragManager";
 
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -25,7 +26,7 @@ export interface FieldViewProps {
     DataDoc?: Doc;
     LibraryPath: Doc[];
     onClick?: ScriptField;
-    dropAction: dropAction;
+    dropAction: dropActionType;
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
     renderDepth: number;
-- 
cgit v1.2.3-70-g09d2


From 7670987e5d957ff0cae12acd921275faae4041b1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 28 Mar 2020 17:11:37 -0400
Subject: ongoing work with parameterized templates

---
 src/client/util/RichTextSchema.tsx                 | 2 +-
 src/client/views/collections/CollectionSubView.tsx | 8 ++++----
 src/new_fields/Doc.ts                              | 7 ++++---
 src/new_fields/util.ts                             | 4 ++++
 4 files changed, 13 insertions(+), 8 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a81c6515d..29fd28994 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1025,7 +1025,7 @@ export class DashFieldView {
         this._fieldWrapper.appendChild(this._fieldSpan);
         this._fieldWrapper.appendChild(this._enumerables);
         (this as any).dom = this._fieldWrapper;
-        updateText(false);
+        //updateText(false);
     }
     destroy() {
         this._reactionDisposer?.();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 11f214625..8b7136876 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -48,8 +48,8 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     layoutEngine?: () => string;
 }
 
-export function CollectionSubView<T,X>(schemaCtor: (doc: Doc) => T, moreProps?:X) {
-    class CollectionSubView extends DocComponent<X&SubCollectionViewProps, T>(schemaCtor) {
+export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: X) {
+    class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -92,7 +92,7 @@ export function CollectionSubView<T,X>(schemaCtor: (doc: Doc) => T, moreProps?:X
         }
 
         @computed get dataDoc() {
-            return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
+            return (this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
                 this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
@@ -195,7 +195,7 @@ export function CollectionSubView<T,X>(schemaCtor: (doc: Doc) => T, moreProps?:X
         @undoBatch
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
-        const docDragData = de.complete.docDragData;
+            const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
             if (docDragData) {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 9d90c569a..e84ddc684 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -475,8 +475,9 @@ export namespace Doc {
         // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
         // saves it on the data doc indexed by the template layout's id.
         //
+        const params = templateParams?.match(/\(([a-zA-Z0-9_-]*)\)/)?.[1]
         const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
-        const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + "]";
+        const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + params + "]";
         let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
         if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
             expandedTemplateLayout = undefined;
@@ -486,10 +487,10 @@ export namespace Doc {
             setTimeout(action(() => {
                 if (!targetDoc[expandedLayoutFieldKey]) {
                     const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
+                    newLayoutDoc["@params"] = params;
                     newLayoutDoc.expandedTemplate = targetDoc;
                     // the template's parameters are stored in params which are derefenced to find
                     // the actual field key where the template data is stored.  Currently this is only used in RichTextSchema's docView
-                    newLayoutDoc["@params"] = templateParams?.match(/\(([a-zA-Z0-9_-]*)\)/)?.[1];
                     targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
                     const dataDoc = Doc.GetProto(targetDoc);
                     newLayoutDoc.resolvedDataDoc = dataDoc;
@@ -511,7 +512,7 @@ export namespace Doc {
         }
         const existingResolvedDataDoc = childDoc[DataSym] !== Doc.GetProto(childDoc)[DataSym] && childDoc[DataSym];
         const resolvedDataDoc = existingResolvedDataDoc || (Doc.AreProtosEqual(containerDataDoc, containerDoc) || !containerDataDoc || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc);
-        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
+        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc["@params"]) + ")"), data: resolvedDataDoc };
     }
 
     export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 3ab1b299b..0b98be953 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -128,6 +128,10 @@ export function getter(target: any, in_prop: string | symbol | number, receiver:
         }
         if (target.__LAYOUT__) return target.__LAYOUT__[prop];
     }
+    if (typeof prop === "string" && prop.startsWith("@@")) {
+        const expanded = target.__fields["expandedTemplate"];
+        if (expanded) return expanded.cache[target.__fields[prop.substring(1)]];
+    }
     if (prop === "then") {//If we're being awaited
         return undefined;
     }
-- 
cgit v1.2.3-70-g09d2


From 71b0e84cb852bc171a5df96b36cbb5f7bd1c150c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 30 Mar 2020 14:19:03 -0400
Subject: changed expandedTemplate to rootDocument

---
 src/client/util/ProsemirrorExampleTransfer.ts        |  6 +++---
 src/client/util/RichTextSchema.tsx                   |  2 +-
 src/client/views/collections/CollectionSubView.tsx   |  2 +-
 src/client/views/collections/CollectionView.tsx      |  2 +-
 src/client/views/nodes/DocumentView.tsx              |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx          |  4 ++--
 src/client/views/presentationview/PresElementBox.tsx |  2 +-
 src/new_fields/Doc.ts                                | 18 +++++++++---------
 src/new_fields/ScriptField.ts                        |  2 +-
 9 files changed, 20 insertions(+), 20 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 5cbf401d4..42247f177 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -151,14 +151,14 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
     });
     bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.expandedTemplate || layoutDoc;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (originalDoc instanceof Doc) {
             const newDoc = Docs.Create.TextDocument("", {
                 title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
-            originalDoc instanceof Doc && props.addDocument(newDoc);
+            props.addDocument(newDoc);
         }
     });
 
@@ -169,7 +169,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
     };
     const addTextOnRight = (force: boolean) => {
         const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.expandedTemplate || layoutDoc;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
             const newDoc = Docs.Create.TextDocument("", {
                 title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1c637cfbe..b612c82ae 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -849,7 +849,7 @@ export class DashDocView {
             this._renderDisposer?.();
             this._renderDisposer = reaction(() => {
                 if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                    finalLayout.expandedTemplate = dashDoc.aliasOf;
+                    finalLayout.rootDocument = dashDoc.aliasOf;
                 }
                 const layoutKey = StrCast(finalLayout.layoutKey);
                 const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8b7136876..39621b75c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -125,7 +125,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             }
 
             const dfield = this.dataField;
-            const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate && !this.props.annotationsKey ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
+            const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.rootDocument && !this.props.annotationsKey ? [Cast(this.props.Document.rootDocument, Doc, null)] : []);
             const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 324417cf8..edb9fd930 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -306,7 +306,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     }
     get childDocs() {
         const dfield = this.dataField;
-        const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.expandedTemplate, Doc, null) ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
+        const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.rootDocument, Doc, null) ? [Cast(this.props.Document.rootDocument, Doc, null)] : []);
         const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
         const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
         return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 63e3e0431..080c722b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -291,7 +291,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.DeselectAll();
                 UndoManager.RunInBatch(() => this.onClickHandler!.script.run({
                     this: this.props.Document,
-                    self: Cast(this.props.Document.expandedTemplate, Doc, null) || this.props.Document,
+                    self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document,
                     containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
                 }, console.log) && !this.props.Document.dontSelect && !this.props.Document.isButton && this.select(false), "on click");
             } else if (this.Document.type === DocumentType.BUTTON) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f83beade3..bc43beab9 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -389,7 +389,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         const funcs: ContextMenuProps[] = [];
         this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
         funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-        !this.props.Document.expandedTemplate && funcs.push({
+        !this.props.Document.rootDocument && funcs.push({
             description: "Make Template", event: () => {
                 this.props.Document.isTemplateDoc = makeTemplate(this.props.Document, true);
                 Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document);
@@ -822,7 +822,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             }
         }
 
-        const selectOnLoad = (Cast(this.props.Document.expandedTemplate, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad;
+        const selectOnLoad = (Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad;
         if (selectOnLoad && !this.props.dontRegisterView) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 9329a5aa1..758795ed5 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -50,7 +50,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
     _heightDisposer: IReactionDisposer | undefined;
     @computed get indexInPres() { return NumCast(this.presElementDoc?.presentationIndex); }
     @computed get presBoxDoc() { return Cast(this.presElementDoc?.presBox, Doc) as Doc; }
-    @computed get presElementDoc() { return this.props.Document.expandedTemplate as Doc; }
+    @computed get presElementDoc() { return this.props.Document.rootDocument as Doc; }
     @computed get presLayoutDoc() { return this.props.Document; }
     @computed get targetDoc() { return this.presElementDoc?.presentationTargetDoc as Doc; }
     @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6748882c0..8110130b0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -468,10 +468,10 @@ export namespace Doc {
     // templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key.
     // NOTE:  the template will have references to "@params" -- the template arguments will be assigned to the '@params' field
     // so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and
-    // the derefence will then occur on the expandedTemplate (the original document).
+    // the derefence will then occur on the rootDocument (the original document).
     // in the future, field references could be written as @<someparam> and then arguments would be passed in the layout key as:
     //   layout_mytemplate(somparam=somearg).   
-    // then any references to @someparam would be rewritten as accesses to 'somearg' on the expandedTemplate
+    // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
     export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
         const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
         if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc;
@@ -499,8 +499,8 @@ export namespace Doc {
                         const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
                         // the template's arguments are stored in params which is derefenced to find
                         // the actual field key where the parameterized template data is stored.
-                        newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'expandedTemplate' field, but we don't need to reassign their params.  it would be better if the 'expandedTemplate' field could be passed dynamically to avoid have to create instances
-                        newLayoutDoc.expandedTemplate = targetDoc;
+                        newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params.  it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
+                        newLayoutDoc.rootDocument = targetDoc;
                         targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
                         const dataDoc = Doc.GetProto(targetDoc);
                         newLayoutDoc.resolvedDataDoc = dataDoc;
@@ -511,7 +511,7 @@ export namespace Doc {
                 }), 0);
             }
         }
-        return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplate is pending.
+        return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending.
     }
 
     // if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
@@ -882,10 +882,10 @@ export namespace Doc {
             Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
         }
         const options = optionsCollection as Doc;
-        const targetDoc = doc && Doc.GetProto(Cast(doc.expandedTemplate, Doc, null) || doc);
-        targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this)["${enumeratedFieldKey}"])?._backgroundColor || "white"`, undefined, { options }));
-        targetDoc && (targetDoc.color = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this)["${enumeratedFieldKey}"]).color || "black"`, undefined, { options }));
-        targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this)["${enumeratedFieldKey}"]).borderRounding`, undefined, { options }));
+        const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
+        targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?._backgroundColor || "white"`, undefined, { options }));
+        targetDoc && (targetDoc.color = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"]).color || "black"`, undefined, { options }));
+        targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"]).borderRounding`, undefined, { options }));
         enumerations.map(enumeration => {
             const found = DocListCast(options.data).find(d => d.title === enumeration.title);
             if (found) {
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 954c22a8d..148886848 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -131,7 +131,7 @@ export class ScriptField extends ObjectField {
 export class ComputedField extends ScriptField {
     _lastComputedResult: any;
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
-    value = computedFn((doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.expandedTemplate, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result);
+    value = computedFn((doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result);
     public static MakeScript(script: string, params: object = {}) {
         const compiled = ScriptField.CompileScript(script, params, false);
         return compiled.compiled ? new ComputedField(compiled) : undefined;
-- 
cgit v1.2.3-70-g09d2


From 503cae6db116a680fba7bdd530ae30b7b3c495a4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 31 Mar 2020 14:25:25 -0400
Subject: cleaned up some code very slightly

---
 src/client/views/collections/CollectionSubView.tsx | 13 +++++++++++--
 src/new_fields/Doc.ts                              |  7 ++++---
 src/new_fields/util.ts                             |  3 ++-
 3 files changed, 17 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 39621b75c..88cfde0b6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -124,8 +124,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 filterFacets[key][value] = modifiers;
             }
 
-            const dfield = this.dataField;
-            const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.rootDocument && !this.props.annotationsKey ? [Cast(this.props.Document.rootDocument, Doc, null)] : []);
+            let rawdocs: (Doc | Promise<Doc>)[] = [];
+            if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
+                rawdocs = [this.dataField];
+            } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it.  
+                rawdocs = Cast(this.dataField, listSpec(Doc), null);
+            } else {   // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
+                // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
+                // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
+                const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
+                rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
+            }
             const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 8110130b0..7fb17c788 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -883,9 +883,10 @@ export namespace Doc {
         }
         const options = optionsCollection as Doc;
         const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
-        targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?._backgroundColor || "white"`, undefined, { options }));
-        targetDoc && (targetDoc.color = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"]).color || "black"`, undefined, { options }));
-        targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"]).borderRounding`, undefined, { options }));
+        const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
+        targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
+        targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
+        targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
         enumerations.map(enumeration => {
             const found = DocListCast(options.data).find(d => d.title === enumeration.title);
             if (found) {
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 3ab1b299b..3a1fd41f8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,8 +12,9 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
+let tracing = false;
 export function TraceMobx() {
-    // trace();
+    tracing && trace();
 }
 
 export interface GetterResult {
-- 
cgit v1.2.3-70-g09d2


From 4859d4e3f5827c3341d43d19a5309d2ec25ab67b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Apr 2020 17:15:14 -0400
Subject: fixed warnings and some compile errors.  Made a key value layout and
 extened DocumentBox to have a childLayoutKey field

---
 src/client/ClientRecommender.tsx                   |  54 +++++-----
 src/client/cognitive_services/CognitiveServices.ts |  36 +++----
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/RichTextSchema.tsx                 |  18 ++--
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/EditableView.tsx                  |  22 ++--
 src/client/views/GestureOverlay.tsx                |   8 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/MainView.tsx                      |   2 +
 src/client/views/RecommendationsBox.tsx            |  16 +--
 src/client/views/TemplateMenu.tsx                  |   8 +-
 src/client/views/TouchScrollableMenu.tsx           |   4 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 .../views/collections/CollectionSchemaCells.tsx    |   2 +
 .../views/collections/CollectionSchemaView.tsx     |   8 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   3 +-
 .../views/collections/CollectionStaffView.tsx      |  12 +--
 src/client/views/collections/CollectionSubView.tsx |   3 +-
 .../views/collections/CollectionTimeView.tsx       |   2 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/CollectionViewChromes.tsx    |  10 +-
 .../CollectionFreeFormLinksView.tsx                | 116 ++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx |   4 +-
 src/client/views/nodes/ButtonBox.tsx               |   2 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |   2 +
 src/client/views/nodes/DocumentBox.tsx             |   3 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  24 +++--
 src/client/views/nodes/KeyValuePair.tsx            |   2 +
 src/client/views/nodes/RadialMenuItem.tsx          |  30 +++---
 src/client/views/nodes/ScreenshotBox.tsx           |   2 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/client/views/webcam/DashWebRTCVideo.tsx        |   8 +-
 src/mobile/MobileInterface.tsx                     |   2 +
 src/new_fields/Doc.ts                              |   4 +-
 src/new_fields/util.ts                             |   2 +-
 .../Session/agents/applied_session_agent.ts        |   2 +-
 src/server/DashSession/Session/agents/monitor.ts   |   2 +-
 .../Session/agents/promisified_ipc_manager.ts      |   6 +-
 src/server/Websocket/Websocket.ts                  |   8 +-
 src/server/database.ts                             |   2 +-
 src/server/server_Initialization.ts                |   2 +-
 43 files changed, 232 insertions(+), 218 deletions(-)

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

diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 0e67a6e57..537e331ab 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -69,7 +69,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     private distance(vector1: number[], vector2: number[], metric: string = "cosine") {
-        assert(vector1.length === vector2.length, "Vectors are not the same length");
+        // assert(vector1.length === vector2.length, "Vectors are not the same length");
         let similarity: number;
         switch (metric) {
             case "cosine":
@@ -113,7 +113,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             }
         }
         );
-        let doclist = Array.from(ClientRecommender.Instance.docVectors);
+        const doclist = Array.from(ClientRecommender.Instance.docVectors);
         if (distance_metric === "euclidian") {
             doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score);
         }
@@ -169,12 +169,12 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     generateMetadata = async (dataDoc: Doc, extDoc: Doc, threshold: Confidence = Confidence.Excellent) => {
-        let converter = (results: any) => {
-            let tagDoc = new Doc;
-            let tagsList = new List();
+        const converter = (results: any) => {
+            const tagDoc = new Doc;
+            const tagsList = new List();
             results.tags.map((tag: Tag) => {
                 tagsList.push(tag.name);
-                let sanitized = tag.name.replace(" ", "_");
+                const sanitized = tag.name.replace(" ", "_");
                 tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
             });
             extDoc.generatedTags = tagsList;
@@ -193,7 +193,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     private url(dataDoc: Doc) {
-        let data = Cast(Doc.GetProto(dataDoc)[fieldkey], ImageField);
+        const data = Cast(Doc.GetProto(dataDoc)[fieldkey], ImageField);
         return data ? data.url.href : undefined;
     }
 
@@ -215,14 +215,14 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             }
         }
         else {
-            let fielddata = Cast(dataDoc.data, RichTextField);
-            fielddata ? data = fielddata[ToPlainText]() : data = "";
+            const fielddata = Cast(dataDoc.data, RichTextField, null);
+            data = fielddata?.Text || "";
         }
 
         // STEP 2. Upon receiving response from Text Cognitive Services, do additional processing on keywords.
         // Currently we are still using Cognitive Services for internal recommendations, but in the future this might not be necessary. 
 
-        let converter = async (results: any, data: string, isImage: boolean = false) => {
+        const converter = async (results: any, data: string, isImage: boolean = false) => {
             let keyterms = new List<string>(); // raw keywords
             let kp_string: string = ""; // keywords*frequency concatenated into a string. input into TF
             let highKP: string[] = [""]; // most frequent keyphrase
@@ -237,7 +237,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             }
             else { // text processing
                 results.documents.forEach((doc: any) => {
-                    let keyPhrases = doc.keyPhrases; // returned by Cognitive Services
+                    const keyPhrases = doc.keyPhrases; // returned by Cognitive Services
                     keyPhrases.map((kp: string) => {
                         keyterms.push(kp);
                         const frequency = this.countFrequencies(kp, data); // frequency of keyphrase in paragraph
@@ -308,10 +308,10 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     private countFrequencies(keyphrase: string, paragraph: string) {
-        let data = paragraph.split(/ |\n/); // splits by new lines and spaces
-        let kp_array = keyphrase.split(" ");
-        let num_keywords = kp_array.length;
-        let par_length = data.length;
+        const data = paragraph.split(/ |\n/); // splits by new lines and spaces
+        const kp_array = keyphrase.split(" ");
+        const num_keywords = kp_array.length;
+        const par_length = data.length;
         let frequency = 0;
         // slides keyphrase windows across paragraph and checks if it matches with corresponding paragraph slice
         for (let i = 0; i <= par_length - num_keywords; i++) {
@@ -353,8 +353,8 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     bingWebSearch = async (query: string) => {
         const converter = async (results: any) => {
-            let title_vals: string[] = [];
-            let url_vals: string[] = [];
+            const title_vals: string[] = [];
+            const url_vals: string[] = [];
             results.webPages.value.forEach((doc: any) => {
                 title_vals.push(doc.name);
                 url_vals.push(doc.url);
@@ -369,23 +369,23 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     arxivrequest = async (query: string) => {
-        let xhttp = new XMLHttpRequest();
-        let serveraddress = "http://export.arxiv.org/api";
+        const xhttp = new XMLHttpRequest();
+        const serveraddress = "http://export.arxiv.org/api";
         const maxresults = 5;
-        let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=" + maxresults.toString();
-        let promisified = (resolve: any, reject: any) => {
+        const endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=" + maxresults.toString();
+        const promisified = (resolve: any, reject: any) => {
             xhttp.onreadystatechange = function () {
                 if (this.readyState === 4) {
-                    let result = xhttp.response;
-                    let xml = xhttp.responseXML;
+                    const result = xhttp.response;
+                    const xml = xhttp.responseXML;
                     console.log("arXiv Result: ", xml);
                     switch (this.status) {
                         case 200:
-                            let title_vals: string[] = [];
-                            let url_vals: string[] = [];
+                            const title_vals: string[] = [];
+                            const url_vals: string[] = [];
                             //console.log(result);
                             if (xml) {
-                                let titles = xml.getElementsByTagName("title");
+                                const titles = xml.getElementsByTagName("title");
                                 let counter = 1;
                                 if (titles && titles.length > 1) {
                                     while (counter <= maxresults) {
@@ -394,7 +394,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
                                         counter++;
                                     }
                                 }
-                                let ids = xml.getElementsByTagName("id");
+                                const ids = xml.getElementsByTagName("id");
                                 counter = 1;
                                 if (ids && ids.length > 1) {
                                     while (counter <= maxresults) {
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 542ccf04d..3f3726621 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -208,7 +208,7 @@ export namespace CognitiveServices {
                     results.recognitionUnits && (results = results.recognitionUnits);
                 }
                 return results;
-            }
+            };
         }
 
         export interface AzureStrokeData {
@@ -232,13 +232,13 @@ export namespace CognitiveServices {
                 return data;
             },
             requester: async (apiKey: string, query: string) => {
-                let xhttp = new XMLHttpRequest();
-                let serverAddress = "https://api.cognitive.microsoft.com";
-                let endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
-                let promisified = (resolve: any, reject: any) => {
+                const xhttp = new XMLHttpRequest();
+                const serverAddress = "https://api.cognitive.microsoft.com";
+                const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
+                const promisified = (resolve: any, reject: any) => {
                     xhttp.onreadystatechange = function () {
                         if (this.readyState === 4) {
-                            let result = xhttp.responseText;
+                            const result = xhttp.responseText;
                             switch (this.status) {
                                 case 200:
                                     return resolve(result);
@@ -266,7 +266,7 @@ export namespace CognitiveServices {
 
         export namespace Appliers {
             export const analyzer = async (query: string, converter: BingConverter) => {
-                let results = await ExecuteQuery(Service.Bing, Manager, query);
+                const results = await ExecuteQuery(Service.Bing, Manager, query);
                 console.log("Bing results: ", results);
                 const { title_vals, url_vals } = await converter(results);
                 return { title_vals, url_vals };
@@ -281,13 +281,13 @@ export namespace CognitiveServices {
                 return data;
             },
             requester: async (apiKey: string, query: string) => {
-                let xhttp = new XMLHttpRequest();
-                let serverAddress = "https://babel.hathitrust.org/cgi/htd/​";
-                let endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
-                let promisified = (resolve: any, reject: any) => {
+                const xhttp = new XMLHttpRequest();
+                const serverAddress = "https://babel.hathitrust.org/cgi/htd/​";
+                const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
+                const promisified = (resolve: any, reject: any) => {
                     xhttp.onreadystatechange = function () {
                         if (this.readyState === 4) {
-                            let result = xhttp.responseText;
+                            const result = xhttp.responseText;
                             switch (this.status) {
                                 case 200:
                                     return resolve(result);
@@ -315,7 +315,7 @@ export namespace CognitiveServices {
 
         export namespace Appliers {
             export const analyzer = async (query: string, converter: BingConverter) => {
-                let results = await ExecuteQuery(Service.Bing, Manager, query);
+                const results = await ExecuteQuery(Service.Bing, Manager, query);
                 console.log("Bing results: ", results);
                 const { title_vals, url_vals } = await converter(results);
                 return { title_vals, url_vals };
@@ -337,9 +337,9 @@ export namespace CognitiveServices {
                 });
             },
             requester: async (apiKey: string, body: string, service: Service) => {
-                let serverAddress = "https://eastus.api.cognitive.microsoft.com";
-                let endpoint = serverAddress + "/text/analytics/v2.1/keyPhrases";
-                let sampleBody = {
+                const serverAddress = "https://eastus.api.cognitive.microsoft.com";
+                const endpoint = serverAddress + "/text/analytics/v2.1/keyPhrases";
+                const sampleBody = {
                     "documents": [
                         {
                             "language": "en",
@@ -348,7 +348,7 @@ export namespace CognitiveServices {
                         }
                     ]
                 };
-                let actualBody = body;
+                const actualBody = body;
                 const options = {
                     uri: endpoint,
                     body: actualBody,
@@ -368,7 +368,7 @@ export namespace CognitiveServices {
                 console.log("vectorizing...");
                 //keyterms = ["father", "king"];
 
-                let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
+                const args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
                 await requestPromise.post(args).then(async (wordvecs) => {
                     if (wordvecs) {
                         const indices = Object.keys(wordvecs);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dbea8062e..96425ba30 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -266,7 +266,7 @@ export namespace Docs {
                 options: { _width: 40, _height: 40, borderRounding: "100%" },
             }],
             [DocumentType.RECOMMENDATION, {
-                layout: { view: RecommendationsBox },
+                layout: { view: RecommendationsBox, dataField: data },
                 options: { width: 200, height: 200 },
             }],
             [DocumentType.WEBCAM, {
@@ -365,6 +365,7 @@ export namespace Docs {
             const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
             options.layout = layout.view.LayoutString(layout.dataField);
             const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
+            doc.layout_keyValue = KeyValueBox.LayoutString("");
             return doc;
         }
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a2fb7c11b..81ab95ff5 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -810,7 +810,7 @@ export class DashDocView {
                 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().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
             }, { fireImmediately: true });
-            let doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
+            const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
                 ReactDOM.unmountComponentAtNode(this._dashSpan);
                 ReactDOM.render(<DocumentView
                     Document={finalLayout}
@@ -844,7 +844,7 @@ export class DashDocView {
                         console.log(e);
                     }
                 }
-            }
+            };
             this._renderDisposer?.();
             this._renderDisposer = reaction(() => {
                 if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
@@ -939,7 +939,7 @@ export class DashFieldView {
         this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
         this._fieldCheck.onchange = function (e: any) {
             self._dashDoc![self._fieldKey] = e.target.checked;
-        }
+        };
 
         this._fieldSpan = document.createElement("div");
         this._fieldSpan.id = Utils.GenerateGuid();
@@ -1095,12 +1095,12 @@ export class FootnoteView {
                     "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
                     "Mod-b": toggleMark(schema.marks.strong)
                 }),
-                new Plugin({
-                    view(newView) {
-                        // TODO -- make this work with RichTextMenu
-                        // return FormattedTextBox.getToolTip(newView);
-                    }
-                })
+                    // new Plugin({
+                    //     view(newView) {
+                    //         // TODO -- make this work with RichTextMenu
+                    //         // return FormattedTextBox.getToolTip(newView);
+                    //     }
+                    // })
                 ],
 
             }),
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e673189f9..c6fb9ad0b 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -39,7 +39,7 @@ library.add(faCheckCircle);
 library.add(faCloudUploadAlt);
 library.add(faSyncAlt);
 library.add(faShare);
-library.add(faPhotoVideo)
+library.add(faPhotoVideo);
 
 const cloud: IconProp = "cloud-upload-alt";
 const fetch: IconProp = "sync-alt";
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 4a27425e8..2219966e5 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -4,10 +4,7 @@ import { observer } from 'mobx-react';
 import * as Autosuggest from 'react-autosuggest';
 import { ObjectField } from '../../new_fields/ObjectField';
 import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField';
-import { ContextMenu } from './ContextMenu';
-import { ContextMenuProps } from './ContextMenuItem';
 import "./EditableView.scss";
-import { CollectionTreeView } from './collections/CollectionTreeView';
 
 export interface EditableProps {
     /**
@@ -88,12 +85,12 @@ export class EditableView extends React.Component<EditableProps> {
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Tab") {
             e.stopPropagation();
-            this.finalizeEdit(e.currentTarget.value, e.shiftKey);
+            this.finalizeEdit(e.currentTarget.value, e.shiftKey, false);
             this.props.OnTab && this.props.OnTab(e.shiftKey);
         } else if (e.key === "Enter") {
             e.stopPropagation();
             if (!e.ctrlKey) {
-                this.finalizeEdit(e.currentTarget.value, e.shiftKey);
+                this.finalizeEdit(e.currentTarget.value, e.shiftKey, false);
             } else if (this.props.OnFillDown) {
                 this.props.OnFillDown(e.currentTarget.value);
                 this._editing = false;
@@ -123,10 +120,17 @@ export class EditableView extends React.Component<EditableProps> {
     }
 
     @action
-    private finalizeEdit(value: string, shiftDown: boolean) {
-        this._editing = false;
+    private finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean) {
         if (this.props.SetValue(value, shiftDown)) {
+            this._editing = false;
+            this.props.isEditingCallback?.(false);
+        } else {
+            this._editing = false;
             this.props.isEditingCallback?.(false);
+            !lostFocus && setTimeout(action(() => {
+                this._editing = true;
+                this.props.isEditingCallback?.(true);
+            }), 0);
         }
     }
 
@@ -151,7 +155,7 @@ export class EditableView extends React.Component<EditableProps> {
                         className: "editableView-input",
                         onKeyDown: this.onKeyDown,
                         autoFocus: true,
-                        onBlur: e => this.finalizeEdit(e.currentTarget.value, false),
+                        onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true),
                         onPointerDown: this.stopPropagation,
                         onClick: this.stopPropagation,
                         onPointerUp: this.stopPropagation,
@@ -163,7 +167,7 @@ export class EditableView extends React.Component<EditableProps> {
                     defaultValue={this.props.GetValue()}
                     onKeyDown={this.onKeyDown}
                     autoFocus={true}
-                    onBlur={e => this.finalizeEdit(e.currentTarget.value, false)}
+                    onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)}
                     onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
                     style={{ display: this.props.display, fontSize: this.props.fontSize }}
                 />;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 1eff58948..ea60907f6 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -165,7 +165,7 @@ export default class GestureOverlay extends Touchable {
                 this._holdTimer = setTimeout(() => {
                     console.log("hold");
                     const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
-                    let pt: any = te.touches[te.touches.length - 1];
+                    const pt: any = te.touches[te.touches.length - 1];
                     if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) {
                         target?.dispatchEvent(
                             new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchHoldStart",
@@ -589,7 +589,7 @@ export default class GestureOverlay extends Touchable {
                             for (const wR of wordResults) {
                                 console.log(wR);
                                 if (wR?.recognizedText) {
-                                    possibilities.push(wR?.recognizedText)
+                                    possibilities.push(wR?.recognizedText);
                                 }
                                 possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString));
                             }
@@ -743,16 +743,16 @@ export default class GestureOverlay extends Touchable {
                 {this.elements}
 
                 <div className="clipboardDoc-cont" style={{
-                    transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
                     height: this.height,
                     width: this.height,
                     pointerEvents: this._clipboardDoc ? "unset" : "none",
                     touchAction: this._clipboardDoc ? "unset" : "none",
+                    transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height} px)`,
                 }}>
                     {this._clipboardDoc}
                 </div>
                 <div className="filter-cont" style={{
-                    transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
+                    transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height}px)`,
                     height: this.height,
                     width: this.height,
                     pointerEvents: "none",
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index b59ee7a87..52801b570 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -106,8 +106,8 @@ export default class KeyManager {
     });
 
     private shift = async (keyname: string) => {
-        let stopPropagation = false;
-        let preventDefault = false;
+        const stopPropagation = false;
+        const preventDefault = false;
 
         switch (keyname) {
             // case "~":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a81e0cc2c..8d9be5980 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -521,7 +521,9 @@ export class MainView extends React.Component {
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     fieldKey={"data"}
+                    dropAction={"alias"}
                     annotationsKey={""}
+                    bringToFront={emptyFunction}
                     select={emptyFunction}
                     active={returnFalse}
                     isSelected={returnFalse}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index 262226bac..5ebba0abb 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -6,7 +6,7 @@ import "./RecommendationsBox.scss";
 import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
 import { DocumentIcon } from "./nodes/DocumentIcon";
 import { StrCast, NumCast } from "../../new_fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { DocumentView } from "./nodes/DocumentView";
@@ -31,7 +31,7 @@ library.add(faBullseye, faLink);
 @observer
 export class RecommendationsBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); }
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); }
 
     // @observable private _display: boolean = false;
     @observable private _pageX: number = 0;
@@ -48,17 +48,17 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
 
     @action
     private DocumentIcon(doc: Doc) {
-        let layoutresult = StrCast(doc.type);
+        const layoutresult = StrCast(doc.type);
         let renderDoc = doc;
         //let box: number[] = [];
         if (layoutresult.indexOf(DocumentType.COL) !== -1) {
             renderDoc = Doc.MakeDelegate(renderDoc);
         }
-        let returnXDimension = () => 150;
-        let returnYDimension = () => 150;
-        let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
+        const returnXDimension = () => 150;
+        const returnYDimension = () => 150;
+        const scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
         //let scale = () => 1;
-        let newRenderDoc = Doc.MakeAlias(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+        const newRenderDoc = Doc.MakeAlias(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
         newRenderDoc.height = NumCast(this.props.Document.documentIconHeight);
         newRenderDoc.autoHeight = false;
         const docview = <div>
@@ -66,8 +66,8 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                 fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
                 Document={newRenderDoc}
                 addDocument={returnFalse}
+                LibraryPath={emptyPath}
                 removeDocument={returnFalse}
-                ruleProvider={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 addDocTab={returnFalse}
                 pinToPres={returnFalse}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 83dbd3db3..996928cca 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -114,7 +114,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
         const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
         const noteTypes = DocListCast(noteTypesDoc?.data);
-        const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data)
+        const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
         const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
@@ -170,8 +170,8 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc) {
     if (template.dragFactory) {
         template = Cast(template.dragFactory, Doc, null);
     }
-    let templateTitle = StrCast(template?.title);
-    return templateTitle && DocumentView.makeCustomViewClicked(doc, undefined, Docs.Create.FreeformDocument, templateTitle, template)
+    const templateTitle = StrCast(template?.title);
+    return templateTitle && DocumentView.makeCustomViewClicked(doc, undefined, Docs.Create.FreeformDocument, templateTitle, template);
 });
 
 Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, firstDocTitlte: string) {
@@ -180,4 +180,4 @@ Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, firstDocTitlte: st
     const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
     return StrCast(firstDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
     // return SelectionManager.SelectedDocuments().some(view => StrCast(view.props.Document.layoutKey) === "layout_" + template) ? 'check' : 'unchecked'
-})
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/src/client/views/TouchScrollableMenu.tsx b/src/client/views/TouchScrollableMenu.tsx
index 4bda0818e..969605be9 100644
--- a/src/client/views/TouchScrollableMenu.tsx
+++ b/src/client/views/TouchScrollableMenu.tsx
@@ -44,7 +44,7 @@ export default class TouchScrollableMenu extends React.Component<TouchScrollable
                 <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}>
                 </div>
             </div>
-        )
+        );
     }
 }
 
@@ -54,6 +54,6 @@ export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenu
             <div className="menuItem-cont" onClick={this.props.onClick}>
                 {this.props.text}
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 2ee39bc0d..4e1e76f39 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -513,7 +513,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         dragData.dropAction = doc.dropAction as dropActionType;
                         DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
                     }
-                }
+                };
                 let rendered = false;
                 tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
                     (views) => {
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 79b5d7bb7..f124fe21b 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -157,6 +157,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             Document: this.props.rowProps.original,
             DataDoc: this.props.rowProps.original,
             LibraryPath: [],
+            dropAction: "alias",
+            bringToFront: emptyFunction,
             fieldKey: this.props.rowProps.column.id as string,
             ContainingCollectionView: this.props.CollectionView,
             ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index a4502cced..981438513 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -51,8 +51,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
 
 @observer
 export class CollectionSchemaView extends CollectionSubView(doc => doc) {
-    private _mainCont?: HTMLDivElement;
-    private _startPreviewWidth = 0;
+    private _previewCont?: HTMLDivElement;
     private DIVIDER_WIDTH = 4;
 
     @observable previewDoc: Doc | undefined = undefined;
@@ -64,7 +63,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
 
     private createTarget = (ele: HTMLDivElement) => {
-        this._mainCont = ele;
+        this._previewCont = ele;
         super.CreateDropTarget(ele);
     }
 
@@ -81,12 +80,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     }
 
     onDividerDown = (e: React.PointerEvent) => {
-        this._startPreviewWidth = this.previewWidth();
         setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, action(() => this.toggleExpander()));
     }
     @action
     onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
-        const nativeWidth = this._mainCont!.getBoundingClientRect();
+        const nativeWidth = this._previewCont!.getBoundingClientRect();
         const minWidth = 40;
         const maxWidth = 1000;
         const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 086e0842e..076dd3629 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -106,7 +106,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 fields.delete(header);
                 sectionHeaders.splice(sectionHeaders.indexOf(header), 1);
                 changed = true;
-            })
+            });
         }
         changed && setTimeout(action(() => { if (this.sectionHeaders) { this.sectionHeaders.length = 0; this.sectionHeaders.push(...sectionHeaders); } }), 0);
         return fields;
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 646b433bf..0a48c95e4 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -131,7 +131,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
         const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
         newDoc.heading = heading;
-        return this.props.parent.props.addDocument(newDoc);
+        this.props.parent.props.addDocument(newDoc);
+        return false;
     }
 
     @action
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
index 8c7e113b2..5b9a69bf7 100644
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -1,22 +1,20 @@
 import { CollectionSubView } from "./CollectionSubView";
-import { Transform } from "../../util/Transform";
 import React = require("react");
 import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
-import { Doc } from "../../../new_fields/Doc";
 import { NumCast } from "../../../new_fields/Types";
 import "./CollectionStaffView.scss";
 import { observer } from "mobx-react";
 
 @observer
 export class CollectionStaffView extends CollectionSubView(doc => doc) {
-    private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(0, -this._mainCont.current!.scrollTop);
-    private _mainCont = React.createRef<HTMLDivElement>();
     private _reactionDisposer: IReactionDisposer | undefined;
     @observable private _staves = NumCast(this.props.Document.staves);
 
+    componentWillUnmount() {
+        this._reactionDisposer?.();
+    }
     componentDidMount = () => {
-        this._reactionDisposer = reaction(
-            () => NumCast(this.props.Document.staves),
+        this._reactionDisposer = reaction(() => NumCast(this.props.Document.staves),
             (staves) => runInAction(() => this._staves = staves)
         );
 
@@ -47,7 +45,7 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) {
     }
 
     render() {
-        return <div className="collectionStaffView" ref={this._mainCont}>
+        return <div className="collectionStaffView">
             {this.staves}
             {this.addStaffButton}
         </div>;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 88cfde0b6..70927cf22 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,5 +1,4 @@
 import { action, computed, IReactionDisposer, reaction } from "mobx";
-import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
 import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
@@ -107,7 +106,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
             const { Document, DataDoc } = this.props;
             const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).filter(pair => pair.layout);
-            return validPairs.map(({ data, layout }) => ({ data, layout: layout! })); // this mapping is a bit of a hack to coerce types
+            return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
         get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 64832506b..0d2207b27 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -146,7 +146,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                 back
             </button>
             <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
-        </div>
+        </div>;
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index edb9fd930..b6ce2f3a9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -299,7 +299,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
         const { Document, DataDoc } = this.props;
         const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout);
-        return validPairs.map(({ data, layout }) => ({ data, layout: layout! })); // this mapping is a bit of a hack to coerce types
+        return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
     }
     get childDocList() {
         return Cast(this.dataField, listSpec(Doc));
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 960c6554e..2d565d9db 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -267,10 +267,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
         if (collapsed) return null;
         switch (this.props.type) {
-            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
             default: return null;
         }
     }
@@ -355,7 +355,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                 immediate: (source: Doc[]) => Doc.setChildLayout(this.target, source?.[0]),
                 initialize: emptyFunction,
             };
-            DragManager.StartButtonDrag([this._viewRef.current!], c.script, c.title,
+            DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
                 { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
             return true;
         }, emptyFunction, emptyFunction);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index a1ec0daef..49ca024a2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed, IReactionDisposer } from "mobx";
+import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -10,67 +10,9 @@ import React = require("react");
 import { Utils, emptyFunction } from "../../../../Utils";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { DocumentType } from "../../../documents/DocumentTypes";
-import { StrCast } from "../../../../new_fields/Types";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
-
-    _brushReactionDisposer?: IReactionDisposer;
-    componentDidMount() {
-        // this._brushReactionDisposer = reaction(
-        //     () => {
-        //         let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
-        //         return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
-        //     },
-        //     () => {
-        //         let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
-        //         let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
-        //         views.forEach((dstDoc, i) => {
-        //             views.forEach((srcDoc, j) => {
-        //                 let dstTarg = dstDoc;
-        //                 let srcTarg = srcDoc;
-        //                 let x1 = NumCast(srcDoc.x);
-        //                 let x2 = NumCast(dstDoc.x);
-        //                 let x1w = NumCast(srcDoc.width, -1);
-        //                 let x2w = NumCast(dstDoc.width, -1);
-        //                 if (x1w < 0 || x2w < 0 || i === j) { }
-        //                 else {
-        //                     let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
-        //                         let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
-        //                         return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
-        //                     });
-        //                     let brushAction = (field: (Doc | Promise<Doc>)[]) => {
-        //                         let found = findBrush(field);
-        //                         if (found !== -1) {
-        //                             field.splice(found, 1);
-        //                         }
-        //                     };
-        //                     if (Math.abs(x1 + x1w - x2) < 20) {
-        //                         let linkDoc: Doc = new Doc();
-        //                         linkDoc.title = "Histogram Brush";
-        //                         linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
-        //                         linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
-
-        //                         brushAction = (field: (Doc | Promise<Doc>)[]) => {
-        //                             if (findBrush(field) === -1) {
-        //                                 field.push(linkDoc);
-        //                             }
-        //                         };
-        //                     }
-        //                     if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>();
-        //                     if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>();
-        //                     let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
-        //                     let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
-        //                     brushAction(dstBrushDocs);
-        //                     brushAction(srcBrushDocs);
-        //                 }
-        //             });
-        //         });
-        //     });
-    }
-    componentWillUnmount() {
-        this._brushReactionDisposer && this._brushReactionDisposer();
-    }
     @computed
     get uniqueConnections() {
         const connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
@@ -101,4 +43,60 @@ export class CollectionFreeFormLinksView extends React.Component {
             {this.props.children}
         </div>;
     }
+    // _brushReactionDisposer?: IReactionDisposer;
+    // componentDidMount() {
+    //     this._brushReactionDisposer = reaction(
+    //         () => {
+    //             let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+    //             return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
+    //         },
+    //         () => {
+    //             let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+    //             let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
+    //             views.forEach((dstDoc, i) => {
+    //                 views.forEach((srcDoc, j) => {
+    //                     let dstTarg = dstDoc;
+    //                     let srcTarg = srcDoc;
+    //                     let x1 = NumCast(srcDoc.x);
+    //                     let x2 = NumCast(dstDoc.x);
+    //                     let x1w = NumCast(srcDoc.width, -1);
+    //                     let x2w = NumCast(dstDoc.width, -1);
+    //                     if (x1w < 0 || x2w < 0 || i === j) { }
+    //                     else {
+    //                         let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
+    //                             let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
+    //                             return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
+    //                         });
+    //                         let brushAction = (field: (Doc | Promise<Doc>)[]) => {
+    //                             let found = findBrush(field);
+    //                             if (found !== -1) {
+    //                                 field.splice(found, 1);
+    //                             }
+    //                         };
+    //                         if (Math.abs(x1 + x1w - x2) < 20) {
+    //                             let linkDoc: Doc = new Doc();
+    //                             linkDoc.title = "Histogram Brush";
+    //                             linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
+    //                             linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
+
+    //                             brushAction = (field: (Doc | Promise<Doc>)[]) => {
+    //                                 if (findBrush(field) === -1) {
+    //                                     field.push(linkDoc);
+    //                                 }
+    //                             };
+    //                         }
+    //                         if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>();
+    //                         if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>();
+    //                         let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
+    //                         let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
+    //                         brushAction(dstBrushDocs);
+    //                         brushAction(srcBrushDocs);
+    //                     }
+    //                 });
+    //             });
+    //         });
+    // }
+    // componentWillUnmount() {
+    //     this._brushReactionDisposer?.();
+    // }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0f94bffd6..276a49570 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_
 import { InkField, InkData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
-import { Cast, NumCast, FieldValue } from "../../../../new_fields/Types";
+import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../../Utils";
 import { Docs, DocUtils } from "../../../documents/Documents";
@@ -107,7 +107,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
         } else if (!e.ctrlKey) {
             FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
-            let tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
+            const tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
             const template = FormattedTextBox.DefaultLayout;
             if (template instanceof Doc) {
                 tbox._width = NumCast(template._width);
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index de0b509eb..f1bf7cfcf 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -82,7 +82,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
                 style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
                 <div className="buttonBox-mainButton" style={{
                     background: this.Document.backgroundColor, color: this.Document.color || "inherit",
-                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: this.Document.textTransform || ""
+                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: (this.Document.textTransform as any) || ""
                 }} >
                     <div className="buttonBox-mainButtonCenter">
                         {(this.Document.text || this.Document.title)}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 8632f9c9a..9494a4bc4 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -22,6 +22,7 @@ interface ContentFittingDocumentViewProps {
     childDocs?: Doc[];
     renderDepth: number;
     fitToBox?: boolean;
+    layoutKey?: string;
     dropAction?: dropActionType;
     PanelWidth: () => number;
     PanelHeight: () => number;
@@ -88,6 +89,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         LayoutDoc={this.props.LayoutDoc}
                         LibraryPath={this.props.LibraryPath}
                         fitToBox={this.props.fitToBox}
+                        layoutKey={this.props.layoutKey}
                         dropAction={this.props.dropAction}
                         onClick={this.props.onClick}
                         backgroundColor={this.props.backgroundColor}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 978480142..655dc5de2 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -117,13 +117,14 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
                 DataDocument={undefined}
                 LibraryPath={emptyPath}
                 fitToBox={this.props.fitToBox}
+                layoutKey={StrCast(this.props.Document.childLayoutKey)}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
                 addDocTab={this.props.addDocTab}
                 pinToPres={this.props.pinToPres}
                 getTransform={this.getTransform}
-                renderDepth={this.props.renderDepth + 1} // bcz: need a forceActive prop here ... not the same as renderDepth = 0
+                renderDepth={this.props.renderDepth + 1}
                 PanelWidth={this.pwidth}
                 PanelHeight={this.pheight}
                 focus={this.props.focus}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 8be6ab151..c250da874 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -64,15 +64,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         TraceMobx();
         if (!this.layoutDoc) return "<p>awaiting layout</p>";
         const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");
-        if (layout === undefined) {
-            return this.props.Document.data ?
-                "<FieldView {...props} fieldKey='data' />" :
-                KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : "");
-        } else if (typeof layout === "string") {
-            return layout;
-        } else {
-            return "<p>Loading layout</p>";
-        }
+        if (this.props.layoutKey === "layout_keyValue") {
+            return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
+        } else
+            if (layout === undefined) {
+                return this.props.Document.data ?
+                    "<FieldView {...props} fieldKey='data' />" :
+                    KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : "");
+            } else if (typeof layout === "string") {
+                return layout;
+            } else {
+                return "<p>Loading layout</p>";
+            }
     }
 
     get dataDoc() {
@@ -81,7 +84,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
     get layoutDoc() {
         const params = StrCast(this.props.Document.PARAMS);
-        return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
+        const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
+        return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
     }
 
     CreateBindings(): JsxBindings {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 93bda6d02..3d59ea61a 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,6 +61,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             fieldKey: this.props.keyName,
             isSelected: returnFalse,
             select: emptyFunction,
+            dropAction:"alias",
+            bringToFront:emptyFunction,
             renderDepth: 1,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index fdc732d3f..bd5b3bff4 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -44,12 +44,12 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
 
     setcircle() {
         let circlemin = 0;
-        let circlemax = 1
+        let circlemax = 1;
         this.props.min ? circlemin = this.props.min : null;
         this.props.max ? circlemax = this.props.max : null;
         if (document.getElementById("myCanvas") !== null) {
-            var c: any = document.getElementById("myCanvas");
-            let color = "white"
+            const c: any = document.getElementById("myCanvas");
+            let color = "white";
             switch (circlemin % 3) {
                 case 1:
                     color = "#c2c2c5";
@@ -70,38 +70,38 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
 
             }
             if (c.getContext) {
-                var ctx = c.getContext("2d");
+                const ctx = c.getContext("2d");
                 ctx.beginPath();
                 ctx.arc(150, 150, 150, (circlemin / circlemax) * 2 * Math.PI, ((circlemin + 1) / circlemax) * 2 * Math.PI);
                 ctx.arc(150, 150, 50, ((circlemin + 1) / circlemax) * 2 * Math.PI, (circlemin / circlemax) * 2 * Math.PI, true);
                 ctx.fillStyle = color;
-                ctx.fill()
+                ctx.fill();
             }
         }
     }
 
     calculatorx() {
         let circlemin = 0;
-        let circlemax = 1
+        let circlemax = 1;
         this.props.min ? circlemin = this.props.min : null;
         this.props.max ? circlemax = this.props.max : null;
-        let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
-        let degrees = 360 * avg;
-        let x = 100 * Math.cos(degrees * Math.PI / 180);
-        let y = -125 * Math.sin(degrees * Math.PI / 180);
+        const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
+        const degrees = 360 * avg;
+        const x = 100 * Math.cos(degrees * Math.PI / 180);
+        const y = -125 * Math.sin(degrees * Math.PI / 180);
         return x;
     }
 
     calculatory() {
 
         let circlemin = 0;
-        let circlemax = 1
+        let circlemax = 1;
         this.props.min ? circlemin = this.props.min : null;
         this.props.max ? circlemax = this.props.max : null;
-        let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
-        let degrees = 360 * avg;
-        let x = 125 * Math.cos(degrees * Math.PI / 180);
-        let y = -100 * Math.sin(degrees * Math.PI / 180);
+        const avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2;
+        const degrees = 360 * avg;
+        const x = 125 * Math.cos(degrees * Math.PI / 180);
+        const y = -100 * Math.sin(degrees * Math.PI / 180);
         return y;
     }
 
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 548066f1c..7c58a5148 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -142,7 +142,7 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
     toggleRecording = action(async () => {
         this._screenCapture = !this._screenCapture;
         this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
-    })
+    });
 
     private get uIButtons() {
         return (<div className="screenshotBox-uiButtons">
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 591864f2c..838fbefb1 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -63,7 +63,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
             }
         } else if (field?.url) {
-            var result = await WebRequest.get(Utils.CorsProxy(field.url.href));
+            const result = await WebRequest.get(Utils.CorsProxy(field.url.href));
             this.dataDoc.text = htmlToText.fromString(result.content);
         }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a1e7d5c2a..cc187cd67 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -238,7 +238,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         this._pdfViewer.currentScaleValue = this._zoomed = 1;
         this.gotoPage(this.Document.curPage || 1);
         document.removeEventListener("pagesinit", this.pagesinit);
-    })
+    });
 
     createPdfViewer() {
         if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 9c339e986..1d52ba38f 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -36,7 +36,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
       */
     private onEnterKeyDown = (e: React.KeyboardEvent) => {
         if (e.keyCode === 13) {
-            let submittedTitle = this.roomText!.value;
+            const submittedTitle = this.roomText!.value;
             this.roomText!.value = "";
             this.roomText!.blur();
             initialize(submittedTitle, this.changeUILook);
@@ -56,7 +56,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
     }
 
     render() {
-        let content =
+        const content =
             <div className="webcam-cont" style={{ width: "100%", height: "100%" }}>
                 <div className="webcam-header">DashWebRTC</div>
                 <input id="roomName" type="text" placeholder="Enter room name" ref={(e) => this.roomText = e!} onKeyDown={this.onEnterKeyDown} />
@@ -72,8 +72,8 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
                 </div>
             </div >;
 
-        let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
-        let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+        const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+        const classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
 
 
         return (
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 5d3a517ae..1d2d57b96 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -199,6 +199,8 @@ export default class MobileInterface extends React.Component {
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
                         fieldKey={""}
+                        dropAction={"alias"}
+                        bringToFront={emptyFunction }
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
                         PanelHeight={() => window.innerHeight}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 25b526168..440f13d6b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -353,7 +353,7 @@ export namespace Doc {
     // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
     export function GetProto(doc: Doc): Doc {
         if (doc instanceof Promise) {
-            console.log("GetProto: error: got Promise insead of Doc")
+            console.log("GetProto: error: got Promise insead of Doc");
         }
         const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
         return proto === doc ? proto : Doc.GetProto(proto);
@@ -697,7 +697,7 @@ export namespace Doc {
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field or 'layout' is given.  
     export function Layout(doc: Doc, layout?: Doc): Doc {
-        const overrideLayout = layout && Cast(doc["data-layout[" + layout[Id] + "]"], Doc, null);
+        const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null);
         return overrideLayout || doc[LayoutSym] || doc;
     }
     export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; }
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 3a1fd41f8..8c719ccd8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-let tracing = false;
+const tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
diff --git a/src/server/DashSession/Session/agents/applied_session_agent.ts b/src/server/DashSession/Session/agents/applied_session_agent.ts
index 46c9e22ed..12064668b 100644
--- a/src/server/DashSession/Session/agents/applied_session_agent.ts
+++ b/src/server/DashSession/Session/agents/applied_session_agent.ts
@@ -44,7 +44,7 @@ export abstract class AppliedSessionAgent {
         if (!this.launched) {
             this.launched = true;
             if (isMaster) {
-                this.sessionMonitorRef = Monitor.Create()
+                this.sessionMonitorRef = Monitor.Create();
                 const sessionKey = await this.initializeMonitor(this.sessionMonitorRef);
                 this.sessionMonitorRef.finalize(sessionKey);
             } else {
diff --git a/src/server/DashSession/Session/agents/monitor.ts b/src/server/DashSession/Session/agents/monitor.ts
index 6f8d25614..ee8afee65 100644
--- a/src/server/DashSession/Session/agents/monitor.ts
+++ b/src/server/DashSession/Session/agents/monitor.ts
@@ -167,7 +167,7 @@ export class Monitor extends IPCMessageReceiver {
      * and pass down any variables the pertinent to the child processes as environment variables.
      */
     private loadAndValidateConfiguration = (): Configuration => {
-        let config: Configuration;
+        let config: Configuration | undefined;
         try {
             console.log(this.timestamp(), cyan("validating configuration..."));
             config = JSON.parse(readFileSync('./session.config.json', 'utf8'));
diff --git a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
index 9f0db8330..feff568e1 100644
--- a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
+++ b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
@@ -35,8 +35,8 @@ export type MessageHandler<T = any> = (args: T) => (any | Promise<any>);
  * When a message is emitted, it is embedded with private metadata
  * to facilitate the resolution of promises, etc.
  */
-interface InternalMessage extends Message { metadata: Metadata }
-interface Metadata { isResponse: boolean; id: string }
+interface InternalMessage extends Message { metadata: Metadata; }
+interface Metadata { isResponse: boolean; id: string; }
 type InternalMessageHandler = (message: InternalMessage) => (any | Promise<any>);
 
 /**
@@ -133,7 +133,7 @@ export class PromisifiedIPCManager {
         Object.keys(pendingMessages).forEach(id => {
             const error: ErrorLike = { name: "ManagerDestroyed", message: "The IPC manager was destroyed before the response could be returned." };
             const message: InternalMessage = { name: pendingMessages[id], args: { error }, metadata: { id, isResponse: true } };
-            this.target.send?.(message)
+            this.target.send?.(message);
         });
         this.pendingMessages = {};
     }
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index c5dc22912..9f9fc9619 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -55,8 +55,8 @@ export namespace WebSocket {
             socket.on('create or join', function (room) {
                 console.log('Received request to create or join room ' + room);
 
-                var clientsInRoom = socket.adapter.rooms[room];
-                var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
+                const clientsInRoom = socket.adapter.rooms[room];
+                const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
                 console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
 
                 if (numClients === 0) {
@@ -76,8 +76,8 @@ export namespace WebSocket {
             });
 
             socket.on('ipaddr', function () {
-                var ifaces = networkInterfaces();
-                for (var dev in ifaces) {
+                const ifaces = networkInterfaces();
+                for (const dev in ifaces) {
                     ifaces[dev].forEach(function (details) {
                         if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
                             socket.emit('ipaddr', details.address);
diff --git a/src/server/database.ts b/src/server/database.ts
index 055f04c49..fc91ff3a2 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -328,7 +328,7 @@ export namespace Database {
 
         export const LogUpload = async (information: Upload.ImageInformation) => {
             const bundle = {
-                _id: Utils.GenerateDeterministicGuid(String(information.contentSize!)),
+                _id: Utils.GenerateDeterministicGuid(String(information.contentSize)),
                 ...information
             };
             return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory);
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 7b2228831..1150118f7 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -123,7 +123,7 @@ function registerCorsProxy(server: express.Express) {
     const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
     server.use("/corsProxy", (req, res) => {
 
-        let requrl = decodeURIComponent(req.url.substring(1));
+        const requrl = decodeURIComponent(req.url.substring(1));
         const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : "";
         // cors weirdness here... 
         // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, 
-- 
cgit v1.2.3-70-g09d2


From adabfbea2b034beb310530aef9cc8b206e260b67 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 3 Apr 2020 20:23:15 -0400
Subject: added rootSelected() to make forceActive work better

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/RichTextSchema.tsx                 |  1 +
 src/client/views/DocComponent.tsx                  |  6 +++--
 src/client/views/GestureOverlay.tsx                |  1 +
 src/client/views/MainView.tsx                      |  4 ++++
 src/client/views/OverlayView.tsx                   |  1 +
 src/client/views/Palette.tsx                       | 26 +++++++---------------
 src/client/views/RecommendationsBox.tsx            |  1 +
 src/client/views/SearchDocBox.tsx                  |  3 ++-
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionLinearView.tsx     |  3 ++-
 .../views/collections/CollectionSchemaView.tsx     |  1 +
 .../views/collections/CollectionStackingView.tsx   |  1 +
 src/client/views/collections/CollectionSubView.tsx |  5 +++++
 .../views/collections/CollectionTreeView.tsx       |  2 ++
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 -
 src/client/views/nodes/AudioBox.tsx                | 12 +++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 +++---
 .../views/nodes/ContentFittingDocumentView.tsx     |  1 +
 src/client/views/nodes/DocumentBox.tsx             |  1 +
 src/client/views/nodes/DocumentContentsView.tsx    |  4 ++--
 src/client/views/nodes/DocumentView.tsx            | 24 +++++++++++++++-----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 +
 src/client/views/pdf/PDFViewer.tsx                 |  3 +--
 .../views/presentationview/PresElementBox.tsx      |  3 ++-
 src/client/views/search/SearchItem.tsx             |  3 ++-
 src/mobile/MobileInterface.tsx                     |  2 ++
 .../authentication/models/current_user_utils.ts    |  2 +-
 31 files changed, 83 insertions(+), 43 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a69bfae01..0a2269bac 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -435,7 +435,7 @@ export namespace Docs {
         Scripting.addGlobal(Buxton);
 
         const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
-            "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
+            "_chromeStatus", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
             "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", "_scrollTop",
             "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"];
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 094cd58f3..4a930177d 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -818,6 +818,7 @@ export class DashDocView {
                     LibraryPath={this._textBox.props.LibraryPath}
                     fitToBox={BoolCast(dashDoc._fitToBox)}
                     addDocument={returnFalse}
+                    rootSelected={this._textBox.props.isSelected}
                     removeDocument={removeDoc}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox.props.addDocTab}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index f4e830a48..454c245cc 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -29,6 +29,7 @@ interface DocExtendableProps {
     fieldKey: string;
     isSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
+    rootSelected: () => boolean;
 }
 export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
@@ -36,7 +37,7 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
         @computed get Document(): T { return schemaCtor(this.props.Document); }
         @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
         @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Cast(this.props.Document.resolvedDataDoc, Doc, null) || Doc.GetProto(this.props.Document)) as Doc; }
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected()) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
     }
     return Component;
 }
@@ -50,6 +51,7 @@ export interface DocAnnotatableProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     isSelected: (outsideReaction?: boolean) => boolean;
+    rootSelected: () => boolean;
     renderDepth: number;
 }
 export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
@@ -86,7 +88,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            ((this.props.Document.forceActive && this.props.rootSelected()) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index ea60907f6..26bee9a6f 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -705,6 +705,7 @@ export default class GestureOverlay extends Touchable {
                 LibraryPath={emptyPath}
                 addDocument={undefined}
                 addDocTab={returnFalse}
+                rootSelected={returnTrue}
                 pinToPres={emptyFunction}
                 onClick={undefined}
                 removeDocument={undefined}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8d9be5980..fae520e40 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -309,6 +309,7 @@ export class MainView extends React.Component {
             addDocument={undefined}
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
+            rootSelected={returnTrue}
             onClick={undefined}
             backgroundColor={this.defaultBackgroundColors}
             removeDocument={undefined}
@@ -407,6 +408,7 @@ export class MainView extends React.Component {
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     addDocument={undefined}
+                    rootSelected={returnTrue}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
@@ -435,6 +437,7 @@ export class MainView extends React.Component {
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
+                    rootSelected={returnTrue}
                     removeDocument={returnFalse}
                     onClick={undefined}
                     ScreenToLocalTransform={this.mainContainerXf}
@@ -523,6 +526,7 @@ export class MainView extends React.Component {
                     fieldKey={"data"}
                     dropAction={"alias"}
                     annotationsKey={""}
+                    rootSelected={returnTrue}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     active={returnFalse}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 220efd4a8..7587071db 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -174,6 +174,7 @@ export class OverlayView extends React.Component {
                     Document={d}
                     LibraryPath={emptyPath}
                     ChromeHeight={returnZero}
+                    rootSelected={returnTrue}
                     // isSelected={returnFalse}
                     // select={emptyFunction}
                     // layoutKey={"layout"}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index e04f814d1..674b27918 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -1,23 +1,12 @@
+import { IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
 import * as React from "react";
-import "./Palette.scss";
-import { PointData } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
-import { Docs } from "../documents/Documents";
-import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
-import { List } from "../../new_fields/List";
-import { DocumentView } from "./nodes/DocumentView";
-import { emptyPath, returnFalse, emptyFunction, returnOne, returnEmptyString, returnTrue } from "../../Utils";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { NumCast } from "../../new_fields/Types";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue } from "../../Utils";
 import { Transform } from "../util/Transform";
-import { computed, action, IReactionDisposer, reaction, observable } from "mobx";
-import { FieldValue, Cast, NumCast } from "../../new_fields/Types";
-import { observer } from "mobx-react";
-import { DocumentContentsView } from "./nodes/DocumentContentsView";
-import { CollectionStackingView } from "./collections/CollectionStackingView";
-import { CollectionView } from "./collections/CollectionView";
-import { CollectionSubView, SubCollectionViewProps } from "./collections/CollectionSubView";
-import { makeInterface } from "../../new_fields/Schema";
-import { documentSchema } from "../../new_fields/documentSchemas";
+import { DocumentView } from "./nodes/DocumentView";
+import "./Palette.scss";
 
 export interface PaletteProps {
     x: number;
@@ -40,7 +29,7 @@ export default class Palette extends React.Component<PaletteProps> {
     }
 
     componentWillUnmount = () => {
-        this._selectedDisposer && this._selectedDisposer();
+        this._selectedDisposer?.();
     }
 
     render() {
@@ -54,6 +43,7 @@ export default class Palette extends React.Component<PaletteProps> {
                             LibraryPath={emptyPath}
                             addDocument={undefined}
                             addDocTab={returnFalse}
+                            rootSelected={returnTrue}
                             pinToPres={emptyFunction}
                             removeDocument={undefined}
                             onClick={undefined}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index 5ebba0abb..bf8de36b4 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -68,6 +68,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                 addDocument={returnFalse}
                 LibraryPath={emptyPath}
                 removeDocument={returnFalse}
+                rootSelected={returnFalse}
                 ScreenToLocalTransform={Transform.Identity}
                 addDocTab={returnFalse}
                 pinToPres={returnFalse}
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
index c57f9e737..e66de39d4 100644
--- a/src/client/views/SearchDocBox.tsx
+++ b/src/client/views/SearchDocBox.tsx
@@ -410,8 +410,9 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
             <div style={{ pointerEvents: "all" }}>
                 <ContentFittingDocumentView {...this.props}
                     Document={this.content}
+                    rootSelected={returnFalse}
                     getTransform={this.props.ScreenToLocalTransform}>
-                    </ContentFittingDocumentView>
+                </ContentFittingDocumentView>
                 <div
                     style={{
                         position: "absolute",
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4e1e76f39..4209bd574 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -788,6 +788,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             Document={document}
             DataDoc={resolvedDataDoc}
             bringToFront={emptyFunction}
+            rootSelected={returnTrue}
             addDocument={undefined}
             removeDocument={undefined}
             ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 344605412..a6ada75b2 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
 import { makeInterface } from '../../../new_fields/Schema';
 import { BoolCast, NumCast, StrCast, Cast } from '../../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { Transform } from '../../util/Transform';
 import "./CollectionLinearView.scss";
@@ -110,6 +110,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 moveDocument={this.props.moveDocument}
                                 addDocTab={this.props.addDocTab}
                                 pinToPres={emptyFunction}
+                                rootSelected={this.props.isSelected}
                                 removeDocument={this.props.removeDocument}
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 981438513..a1b541f74 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -124,6 +124,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 LibraryPath={this.props.LibraryPath}
                 childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
+                rootSelected={this.rootSelected}
                 PanelWidth={this.previewWidth}
                 PanelHeight={this.previewHeight}
                 getTransform={this.getPreviewTransform}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 076dd3629..d21e17bbc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -176,6 +176,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             LibraryPath={this.props.LibraryPath}
             renderDepth={this.props.renderDepth + 1}
             fitToBox={this.props.fitToBox}
+            rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 70927cf22..d1d6ae3c1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelHeight: () => number;
     VisibleHeight?: () => number;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
+    rootSelected: () => boolean;
     fieldKey: string;
 }
 
@@ -95,6 +96,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
+        rootSelected = () => {
+            return this.props.isSelected() || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected() : false);
+        }
+
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
         // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
         // to its children which may be templates.
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6ee48f11b..fc612c66d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -373,6 +373,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     DataDocument={this.templateDataDoc}
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
+                    rootSelected={returnTrue}
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     PanelWidth={this.docWidth}
@@ -454,6 +455,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         LibraryPath={this.props.libraryPath || []}
                         addDocument={undefined}
                         addDocTab={this.props.addDocTab}
+                        rootSelected={returnTrue}
                         pinToPres={emptyFunction}
                         onClick={this.props.onChildClick || editTitle}
                         dropAction={this.props.dropAction}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index df1770ffe..3913ccd88 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -117,7 +117,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected() && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
 
     whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2871fe192..a164e1794 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -832,6 +832,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             Document: childLayout,
             LibraryPath: this.libraryPath,
             layoutKey: undefined,
+            rootSelected: this.rootSelected,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 276a49570..503df10c2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -5,7 +5,6 @@ import { InkField, InkData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
 import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../../Utils";
 import { Docs, DocUtils } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 03b2a2297..bd54d64ff 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -7,7 +7,7 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField";
 import { DocExtendableComponent } from "../DocComponent";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
 import { DateField } from "../../../new_fields/DateField";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -258,9 +258,15 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                                 return !linkTime ? (null) :
                                     <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
                                         <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
-                                            <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
+                                            <DocumentView {...this.props}
+                                                Document={l}
+                                                rootSelected={returnFalse}
+                                                layoutKey={Doc.LinkEndpoint(l, la2)}
                                                 ContainingCollectionDoc={this.props.Document}
-                                                parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
+                                                parentActive={returnTrue}
+                                                bringToFront={emptyFunction}
+                                                zoomToScale={emptyFunction}
+                                                getScale={returnOne}
                                                 backgroundColor={returnTransparent} />
                                         </div>
                                         <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index eaab4086c..356192797 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -89,14 +89,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
 
-            {!this.props.fitToBox ? <DocumentView {...this.props}
+            {!this.props.fitToBox ? 
+            <DocumentView {...this.props}
                 dragDivName={"collectionFreeFormDocumentView-container"}
                 ContentScaling={this.contentScaling}
                 ScreenToLocalTransform={this.getTransform}
                 backgroundColor={this.props.backgroundColor}
                 PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
-            /> : <ContentFittingDocumentView {...this.props}
+                PanelHeight={this.panelHeight} /> 
+            : <ContentFittingDocumentView {...this.props}
                 CollectionDoc={this.props.ContainingCollectionDoc}
                 DataDocument={this.props.DataDoc}
                 getTransform={this.getTransform}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 9494a4bc4..fdf2a9551 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -40,6 +40,7 @@ interface ContentFittingDocumentViewProps {
     addDocTab: (document: Doc, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     dontRegisterView?: boolean;
+    rootSelected: () => boolean;
 }
 
 @observer
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index debe104d7..47118e758 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -122,6 +122,7 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
             CollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
             fitToBox={this.props.fitToBox}
             layoutKey={"layout_" + childTemplateName}
+            rootSelected={this.props.isSelected}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fc61487e5..687137adc 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt } from "../../../new_fields/Doc";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Without } from "../../../Utils";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
@@ -62,7 +62,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     forceLayout?: string,
     forceFieldKey?: string,
     hideOnLeave?: boolean,
-    makeLink?: () => Opt<Doc>;
+    makeLink?: () => Opt<Doc>,  // function to call when a link is made
 }> {
     @computed get layout(): string {
         TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1e02ab44e..42f5fb946 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -60,6 +60,7 @@ export interface DocumentViewProps {
     LayoutDoc?: () => Opt<Doc>;
     LibraryPath: Doc[];
     fitToBox?: boolean;
+    rootSelected: () => boolean; // whether the root of a template has been selected
     onClick?: ScriptField;
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
@@ -277,7 +278,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.dontDecorateSelection = this.props.Document.dontDecorateSelection && (!e.ctrlKey || e.button < 2);
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
-            e.stopPropagation();
+            let stopPropagate = true;
             let preventDefault = true;
             this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
@@ -301,9 +302,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
                 UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow");
             } else {
-                SelectionManager.SelectDoc(this, e.ctrlKey);
+                if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) {
+                    stopPropagate = false;
+                } else {
+                    SelectionManager.SelectDoc(this, e.ctrlKey);
+                }
                 preventDefault = false;
             }
+            stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
         }
     }
@@ -928,6 +934,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const fallback = Cast(this.props.Document.layoutKey, "string");
         return typeof fallback === "string" ? fallback : "layout";
     }
+    rootSelected = () => {
+        return this.isSelected(false) || (this.props.Document.forceActive && this.props.rootSelected?.() ? true : false);
+    }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     @computed get contents() {
         TraceMobx();
@@ -937,6 +946,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DataDoc={this.props.DataDoc}
             LayoutDoc={this.props.LayoutDoc}
             makeLink={this.makeLink}
+            rootSelected={this.rootSelected}
             fitToBox={this.props.fitToBox}
             LibraryPath={this.props.LibraryPath}
             addDocument={this.props.addDocument}
@@ -985,9 +995,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 {StrCast(this.props.Document.title)}
                 {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
                     <div className="documentView-docuLinkWrapper" style={{ position: "absolute", top: 0, left: 0 }} key={`${d[Id]}`}>
-                        <DocumentView {...this.props} Document={d} ContainingCollectionDoc={this.props.Document}
-                            PanelWidth={returnOne} PanelHeight={returnOne} layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
-                            backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
+                        <DocumentView {...this.props}
+                            Document={d}
+                            ContainingCollectionDoc={this.props.Document}
+                            PanelWidth={returnOne} PanelHeight={returnOne}
+                            layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
+                            backgroundColor={returnTransparent}
+                            removeDocument={undoBatch(doc => doc.hidden = true)} />
                     </div>)}
             </div>;
         }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 0305f43d5..13a1becf7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,6 +29,7 @@ export interface FieldViewProps {
     dropAction: dropActionType;
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
+    rootSelected: () => boolean;
     renderDepth: number;
     addDocument?: (document: Doc) => boolean;
     addDocTab: (document: Doc, where: string) => boolean;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 61df188f8..d1a563494 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -181,6 +181,7 @@ export class FormattedTextBoxComment {
                                     LibraryPath={emptyPath}
                                     fitToBox={true}
                                     moveDocument={returnFalse}
+                                    rootSelected={returnFalse}
                                     getTransform={Transform.Identity}
                                     active={returnFalse}
                                     addDocument={returnFalse}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 71495d95f..c032f019d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -31,8 +31,6 @@ import { InkingControl } from "../InkingControl";
 import { InkTool } from "../../../new_fields/InkField";
 import { TraceMobx } from "../../../new_fields/util";
 import { PdfField } from "../../../new_fields/URLField";
-import { PDFBox } from "../nodes/PDFBox";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
 import { DocumentView } from "../nodes/DocumentView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
@@ -61,6 +59,7 @@ interface IViewerProps {
     PanelHeight: () => number;
     ContentScaling: () => number;
     select: (isCtrlPressed: boolean) => void;
+    rootSelected: () => boolean;
     startupLive: boolean;
     renderDepth: number;
     focus: (doc: Doc) => void;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 758795ed5..7ad3474e8 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -9,7 +9,7 @@ import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from "../../../new_fields/FieldSymbols";
 import { createSchema, makeInterface } from '../../../new_fields/Schema';
 import { Cast, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionView';
 import { DocExtendableComponent } from '../DocComponent';
@@ -175,6 +175,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
                     DataDocument={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     LibraryPath={emptyPath}
                     fitToBox={true}
+                    rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     addDocTab={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 63cef5101..0d77026ad 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -158,6 +158,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                 <ContentFittingDocumentView
                     Document={this.props.doc}
                     LibraryPath={emptyPath}
+                    rootSelected={returnFalse}
                     fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 1d2d57b96..98a6d384e 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -124,6 +124,7 @@ export default class MobileInterface extends React.Component {
                 addDocument={returnFalse}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
+                rootSelected={returnFalse}
                 removeDocument={undefined}
                 onClick={undefined}
                 ScreenToLocalTransform={Transform.Identity}
@@ -285,6 +286,7 @@ export default class MobileInterface extends React.Component {
                         addDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
+                        rootSelected={returnFalse}
                         removeDocument={undefined}
                         onClick={undefined}
                         ScreenToLocalTransform={Transform.Identity}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9a90ce17f..7fe623f18 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -213,7 +213,7 @@ export class CurrentUserUtils {
             _width: 35, _height: 25, title: "Tools", fontSize: 10, targetContainer: sidebarContainer, dontDecorateSelection: true,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
-                _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack"
+                _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
             }),
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"),
         });
-- 
cgit v1.2.3-70-g09d2


From 48478855cc28aa423b2d1e482bd181c8c5066da6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 5 Apr 2020 15:13:25 -0400
Subject: adding nativeWidth as props stuff

---
 src/client/util/RichTextSchema.tsx                 |  4 +-
 src/client/views/GestureOverlay.tsx                | 13 ++++---
 src/client/views/MainView.tsx                      | 10 ++++-
 src/client/views/OverlayView.tsx                   |  2 +
 src/client/views/Palette.tsx                       |  4 +-
 src/client/views/RecommendationsBox.tsx            |  4 +-
 src/client/views/TemplateMenu.tsx                  |  6 ++-
 .../views/collections/CollectionDockingView.tsx    |  4 +-
 .../views/collections/CollectionLinearView.tsx     |  6 ++-
 .../views/collections/CollectionSchemaCells.tsx    |  2 +
 .../views/collections/CollectionStackingView.tsx   | 32 +++++++++------
 src/client/views/collections/CollectionSubView.tsx |  8 ++--
 .../views/collections/CollectionTimeView.tsx       |  5 +--
 .../views/collections/CollectionTreeView.tsx       |  2 +
 src/client/views/collections/CollectionView.tsx    |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 ++-
 src/client/views/nodes/AudioBox.tsx                |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 45 +++++++++++++---------
 .../views/nodes/ContentFittingDocumentView.tsx     | 25 +++++++-----
 src/client/views/nodes/DocumentView.tsx            | 14 +++++--
 src/client/views/nodes/FieldView.tsx               |  2 +
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +-
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 src/client/views/nodes/KeyValuePair.tsx            |  6 ++-
 src/client/views/nodes/LinkBox.tsx                 |  2 +
 src/client/views/nodes/ScreenshotBox.tsx           |  4 +-
 src/client/views/nodes/VideoBox.tsx                |  4 +-
 src/client/views/nodes/WebBox.tsx                  |  4 +-
 src/client/views/pdf/PDFViewer.tsx                 |  4 +-
 src/mobile/MobileInterface.tsx                     | 14 +++++--
 src/new_fields/documentSchemas.ts                  |  2 +-
 src/new_fields/util.ts                             |  2 +-
 32 files changed, 170 insertions(+), 81 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index d4568b17f..de2707d36 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -16,7 +16,7 @@ import { listSpec } from "../../new_fields/Schema";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { Docs } from "../documents/Documents";
 import { CollectionViewType } from "../views/collections/CollectionView";
@@ -824,6 +824,8 @@ export class DashDocView {
                     addDocTab={this._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={self._textBox.props.renderDepth + 1}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={finalLayout[WidthSym]}
                     PanelHeight={finalLayout[HeightSym]}
                     focus={this.outerFocus}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 68f1d13fb..110b61efe 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -15,7 +15,7 @@ import { Scripting } from "../util/Scripting";
 import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import HorizontalPalette from "./Palette";
-import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange } from "../../Utils";
+import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils";
 import { DocumentView } from "./nodes/DocumentView";
 import { Transform } from "../util/Transform";
 import { DocumentContentsView } from "./nodes/DocumentContentsView";
@@ -695,7 +695,8 @@ export default class GestureOverlay extends Touchable {
             </svg>]
         ];
     }
-
+    screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1);
+    return300 = () => 300;
     @action
     public openFloatingDoc = (doc: Doc) => {
         this._clipboardDoc =
@@ -709,10 +710,12 @@ export default class GestureOverlay extends Touchable {
                 pinToPres={emptyFunction}
                 onClick={undefined}
                 removeDocument={undefined}
-                ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
+                ScreenToLocalTransform={this.screenToLocalTransform}
                 ContentScaling={returnOne}
-                PanelWidth={() => 300}
-                PanelHeight={() => 300}
+                PanelWidth={this.return300}
+                PanelHeight={this.return300}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 renderDepth={0}
                 backgroundColor={returnEmptyString}
                 focus={emptyFunction}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 420cc95ec..bef92f0fd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -13,7 +13,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { TraceMobx } from '../../new_fields/util';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils';
 import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
@@ -307,6 +307,8 @@ export class MainView extends React.Component {
             removeDocument={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
             PanelWidth={this.getPWidth}
             PanelHeight={this.getPHeight}
             renderDepth={0}
@@ -405,6 +407,8 @@ export class MainView extends React.Component {
                     onClick={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getPHeight}
                     renderDepth={0}
@@ -424,6 +428,8 @@ export class MainView extends React.Component {
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     rootSelected={returnTrue}
                     removeDocument={returnFalse}
                     onClick={undefined}
@@ -524,6 +530,8 @@ export class MainView extends React.Component {
                     onClick={undefined}
                     ScreenToLocalTransform={this.buttonBarXf}
                     ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getContentsHeight}
                     renderDepth={0}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7cfae0bbb..4000cade5 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -182,6 +182,8 @@ export class OverlayView extends React.Component {
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={returnOne}
                     PanelHeight={returnOne}
                     ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index d088d5bba..63744cb50 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import * as React from "react";
 import { Doc } from "../../new_fields/Doc";
 import { NumCast } from "../../new_fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { DocumentView } from "./nodes/DocumentView";
 import "./Palette.scss";
@@ -49,6 +49,8 @@ export default class Palette extends React.Component<PaletteProps> {
                             onClick={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
                             PanelWidth={() => window.screen.width}
                             PanelHeight={() => window.screen.height}
                             renderDepth={0}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index ddeda28ac..e66fd3eb4 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -6,7 +6,7 @@ import "./RecommendationsBox.scss";
 import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
 import { DocumentIcon } from "./nodes/DocumentIcon";
 import { StrCast, NumCast } from "../../new_fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath } from "../../Utils";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { DocumentView } from "./nodes/DocumentView";
@@ -73,6 +73,8 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                 addDocTab={returnFalse}
                 pinToPres={returnFalse}
                 renderDepth={1}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 PanelWidth={returnXDimension}
                 PanelHeight={returnYDimension}
                 focus={emptyFunction}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f2fb26679..c72f3681a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast } from "../../new_fields/Doc";
 import { Docs, } from "../documents/Documents";
 import { StrCast, Cast } from "../../new_fields/Types";
 import { CollectionTreeView } from "./collections/CollectionTreeView";
-import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath } from "../../Utils";
+import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { Scripting } from "../util/Scripting";
@@ -149,6 +149,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     select={emptyFunction}
                     renderDepth={1}
                     addDocTab={returnFalse}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={this.return100}
                     PanelHeight={this.return100}
                     treeViewHideHeaderFields={true}
@@ -161,7 +163,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             );
         }
         return <ul className="template-list" style={{ display: "block" }}>
-            <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress}/>
+            <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />
             {templateMenu}
         </ul>;
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 11eecf771..28aaf0c57 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -15,7 +15,7 @@ import { FieldId } from "../../../new_fields/RefField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { emptyFunction, returnOne, returnTrue, Utils } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -794,6 +794,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
             parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 3bd50ad52..cb0206260 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
 import { makeInterface } from '../../../new_fields/Schema';
 import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse } from '../../../Utils';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { Transform } from '../../util/Transform';
 import "./CollectionLinearView.scss";
@@ -114,6 +114,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
                                 ContentScaling={returnOne}
+                                NativeHeight={returnZero}
+                                NativeWidth={returnZero}
                                 PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
                                 PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
                                 renderDepth={this.props.renderDepth + 1}
@@ -123,7 +125,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 whenActiveChanged={emptyFunction}
                                 bringToFront={emptyFunction}
                                 ContainingCollectionView={undefined}
-                                ContainingCollectionDoc={undefined}/>
+                                ContainingCollectionDoc={undefined} />
                         </div>;
                     })}
                 </div>
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 7b16ff546..ae71c54f7 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -172,6 +172,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             whenActiveChanged: emptyFunction,
             PanelHeight: returnZero,
             PanelWidth: returnZero,
+            NativeHeight: returnZero,
+            NativeWidth: returnZero,
             addDocTab: this.props.addDocTab,
             pinToPres: this.props.pinToPres,
             ContentScaling: returnOne
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d21e17bbc..8ceeb66f1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction } from "../../../Utils";
+import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
@@ -24,7 +24,7 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
-import { Docs } from "../../documents/Documents";
+import { DocumentView } from "../nodes/DocumentView";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -53,6 +53,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
 
     children(docs: Doc[], columns?: number) {
+        TraceMobx();
         this._docXfs.length = 0;
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
@@ -115,7 +116,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     componentDidMount() {
         super.componentDidMount();
         this._heightDisposer = reaction(() => {
-            if (this.props.Document._autoHeight) {
+            if (this.props.Document._autoHeight && !this.props.NativeHeight()) {
                 const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
                 if (this.isStackingView) {
                     const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
@@ -168,12 +169,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
         const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
         const height = () => this.getDocHeight(doc);
-        return <ContentFittingDocumentView
+        return <DocumentView
             Document={doc}
-            DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+            DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
+            FreezeDimensions={this.props.freezeDimensions}
             renderDepth={this.props.renderDepth + 1}
             fitToBox={this.props.fitToBox}
             rootSelected={this.rootSelected}
@@ -181,18 +183,22 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
             PanelHeight={height}
-            getTransform={dxf}
+            ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
-            CollectionView={this.props.CollectionView}
+            ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
+            ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
-            active={this.props.active}
+            parentActive={this.props.active}
+            bringToFront={emptyFunction}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            ContentScaling={returnOne}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}>
-        </ContentFittingDocumentView>;
+        </DocumentView>;
     }
 
     getDocWidth(d?: Doc) {
@@ -387,7 +393,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
     }
 
-    @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); }
+
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth) || this.props.NativeWidth() || 0; }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight) || this.props.NativeHeight() || 0; }
+
+    @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1d6ae3c1..32449de5e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -36,12 +36,15 @@ export interface CollectionViewProps extends FieldViewProps {
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
     rootSelected: () => boolean;
     fieldKey: string;
+    NativeWidth: () => number;
+    NativeHeight: () => number;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
-    overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explict list (see LinkBox)
+    freezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
+    overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
     ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
     isAnnotationOverlay?: boolean;
     annotationsKey: string;
@@ -213,9 +216,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
             if (docDragData) {
                 let added = false;
-                if (this.props.Document._freezeOnDrop) {
-                    de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
-                }
                 if (docDragData.dropAction || docDragData.userDropAction) {
                     added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } else if (docDragData.moveDocument) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4f77e8b0e..d9a10cdc8 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -5,7 +5,7 @@ import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
@@ -29,7 +29,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         this.props.Document.onChildClick = undefined;
     }
     componentDidMount() {
-        this.props.Document._freezeOnDrop = true;
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.layoutKey='layout_detailedView'; alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
         this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", containingCollection: Doc.name, shiftKey: "boolean" });
@@ -73,7 +72,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
-            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} freezeDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 77dd9a162..10da50e35 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -465,6 +465,8 @@ class TreeView extends React.Component<TreeViewProps> {
                         ContentScaling={returnOne}
                         PanelWidth={returnZero}
                         PanelHeight={returnZero}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         renderDepth={1}
                         focus={emptyFunction}
                         parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a727da267..5d08a2bd8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils, setupMoveUpEvents, returnFalse } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse, returnZero } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -424,6 +424,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     <CollectionTreeView {...this.props}
                         CollectionView={this}
                         treeViewHideTitle={true}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
                         ignoreFields={this.treeIgnoreFields}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d4e44b9a9..f22f1a304 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -89,8 +89,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
-    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth); }
-    @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); }
+    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
+    @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private easing = () => this.props.Document.panTransformType === "Ease";
@@ -825,6 +825,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
+            FreezeDimensions: this.props.freezeDimensions,
             layoutKey: undefined,
             rootSelected: this.rootSelected,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index be0e1aec4..ff9630273 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -7,7 +7,7 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField";
 import { DocExtendableComponent } from "../DocComponent";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
 import { DateField } from "../../../new_fields/DateField";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -261,6 +261,8 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                                         <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
                                             <DocumentView {...this.props}
                                                 Document={l}
+                                                NativeHeight={returnZero}
+                                                NativeWidth={returnZero}
                                                 rootSelected={returnFalse}
                                                 layoutKey={Doc.LinkEndpoint(l, la2)}
                                                 ContainingCollectionDoc={this.props.Document}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 356192797..8a4842427 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -41,10 +41,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions || this.layoutDoc._freezeChildDimensions; }
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
-    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth); }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight); }
-
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+   
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
             const someView = Cast(this.props.Document.someView, Doc);
@@ -68,6 +69,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         .scale(1 / this.contentScaling())
 
     focusDoc = (doc: Doc) => this.props.focus(doc, false);
+    NativeWidth = () => this.nativeWidth;
+    NativeHeight = () => this.nativeHeight;
     render() {
         TraceMobx();
         return <div className="collectionFreeFormDocumentView-container"
@@ -89,22 +92,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
 
-            {!this.props.fitToBox ? 
-            <DocumentView {...this.props}
-                dragDivName={"collectionFreeFormDocumentView-container"}
-                ContentScaling={this.contentScaling}
-                ScreenToLocalTransform={this.getTransform}
-                backgroundColor={this.props.backgroundColor}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight} /> 
-            : <ContentFittingDocumentView {...this.props}
-                CollectionDoc={this.props.ContainingCollectionDoc}
-                DataDocument={this.props.DataDoc}
-                getTransform={this.getTransform}
-                active={returnFalse}
-                focus={this.focusDoc}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
+            {!this.props.fitToBox ?
+                <DocumentView {...this.props}
+                    dragDivName={"collectionFreeFormDocumentView-container"}
+                    ContentScaling={this.contentScaling}
+                    ScreenToLocalTransform={this.getTransform}
+                    backgroundColor={this.props.backgroundColor}
+                    NativeHeight={this.NativeHeight}
+                    NativeWidth={this.NativeWidth}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight} />
+                : <ContentFittingDocumentView {...this.props}
+                    CollectionDoc={this.props.ContainingCollectionDoc}
+                    DataDocument={this.props.DataDoc}
+                    getTransform={this.getTransform}
+                    NativeHeight={this.NativeHeight}
+                    NativeWidth={this.NativeWidth}
+                    active={returnFalse}
+                    focus={this.focusDoc}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight}
                 />}
         </div>;
     }
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 9ab6826a3..09675bf73 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -18,6 +18,9 @@ interface ContentFittingDocumentViewProps {
     Document?: Doc;
     DataDocument?: Doc;
     LayoutDoc?: () => Opt<Doc>;
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
+    FreezeDimensions?: boolean;
     LibraryPath: Doc[];
     childDocs?: Doc[];
     renderDepth: number;
@@ -47,12 +50,12 @@ interface ContentFittingDocumentViewProps {
 export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     private get layoutDoc() { return this.props.Document && (this.props.LayoutDoc?.() || Doc.Layout(this.props.Document)); }
-    private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); }
-    private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); }
+    private nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || this.props.PanelWidth());
+    private nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || this.props.PanelHeight());
     @computed get scaling() {
-        const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1);
-        if (wscale * this.nativeHeight > this.props.PanelHeight()) {
-            return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1;
+        const wscale = this.props.PanelWidth() / (this.nativeWidth() || this.props.PanelWidth() || 1);
+        if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
+            return (this.props.PanelHeight() / (this.nativeHeight() || this.props.PanelHeight() || 1)) || 1;
         }
         return wscale || 1;
     }
@@ -61,12 +64,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     private PanelWidth = () => this.panelWidth;
     private PanelHeight = () => this.panelHeight;
 
-    @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); }
-    @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); }
+    @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
+    @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
     private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
-    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
-    private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; }
+    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
 
     @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
 
@@ -81,7 +84,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                     style={{
                         transform: `translate(${this.centeringOffset}px, 0px)`,
                         borderRadius: this.borderRounding,
-                        height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
+                        height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight() / this.nativeWidth() * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
                         width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth()
                     }}>
                     <DocumentView {...this.props}
@@ -89,6 +92,8 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         DataDoc={this.props.DataDocument}
                         LayoutDoc={this.props.LayoutDoc}
                         LibraryPath={this.props.LibraryPath}
+                        NativeWidth={this.nativeWidth}
+                        NativeHeight={this.nativeHeight}
                         fitToBox={this.props.fitToBox}
                         layoutKey={this.props.layoutKey}
                         dropAction={this.props.dropAction}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aa3c1b3a2..9c9f2e48e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
 import { action, computed, runInAction, trace, observable } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -56,6 +56,9 @@ export type DocFocusFunc = () => boolean;
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
+    FreezeDimensions?: boolean;
+    NativeWidth: () => number;
+    NativeHeight: () => number;
     Document: Doc;
     DataDoc?: Doc;
     LayoutDoc?: () => Opt<Doc>;
@@ -109,11 +112,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
-    @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
-    @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions || this.layoutDoc._freezeChildDimensions; }
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
     @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
     @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
     @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
+    NativeWidth = () => this.nativeWidth;
+    NativeHeight = () => this.nativeHeight;
 
     private _firstX: number = -1;
     private _firstY: number = -1;
@@ -965,6 +971,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         TraceMobx();
         return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
             ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+            NativeHeight={this.NativeHeight}
+            NativeWidth={this.NativeWidth}
             Document={this.props.Document}
             DataDoc={this.props.DataDoc}
             LayoutDoc={this.props.LayoutDoc}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 13a1becf7..dfcb03985 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -45,6 +45,8 @@ export interface FieldViewProps {
     focus: (doc: Doc) => void;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    NativeHeight: () => number;
+    NativeWidth: () => number;
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
     ChromeHeight?: () => number;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 94195fbd6..37770a2e1 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue, returnZero } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -1216,6 +1216,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={this.sidebarWidth}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
                             annotationsKey={this.annotationKey}
                             isAnnotationOverlay={false}
                             focus={this.props.focus}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 00057055f..a4d3f3d79 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -14,7 +14,7 @@ import { ComputedField } from '../../../new_fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { AudioField, ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { emptyFunction, returnOne, Utils } from '../../../Utils';
+import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { Networking } from '../../Network';
@@ -445,6 +445,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 annotationsKey={this.annotationKey}
                 isAnnotationOverlay={true}
                 focus={this.props.focus}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index ef039c857..6dc4ae578 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -62,13 +62,15 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             rootSelected: returnFalse,
             isSelected: returnFalse,
             select: emptyFunction,
-            dropAction:"alias",
-            bringToFront:emptyFunction,
+            dropAction: "alias",
+            bringToFront: emptyFunction,
             renderDepth: 1,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
+            NativeHeight: returnZero,
+            NativeWidth: returnZero,
             PanelWidth: this.props.PanelWidth,
             PanelHeight: this.props.PanelHeight,
             addDocTab: returnFalse,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 0e327e130..542c86049 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -23,6 +23,8 @@ export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument
             <CollectionTreeView {...this.props}
                 ChromeHeight={returnZero}
                 overrideDocuments={[this.dataDoc]}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)}
                 annotationsKey={""}
                 CollectionView={undefined}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7c58a5148..58ff4971a 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -10,7 +10,7 @@ import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
@@ -170,6 +170,8 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
                 <CollectionFreeFormView {...this.props}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     annotationsKey={this.annotationKey}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 24b66d8f7..d384ad12f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -11,7 +11,7 @@ import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
-import { Utils, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
@@ -352,6 +352,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                 <CollectionFreeFormView {...this.props}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     annotationsKey={this.annotationKey}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 838fbefb1..ea5d601ec 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -9,7 +9,7 @@ import { InkTool } from "../../../new_fields/InkField";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast } from "../../../new_fields/Types";
 import { WebField } from "../../../new_fields/URLField";
-import { Utils, returnOne, emptyFunction } from "../../../Utils";
+import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -335,6 +335,8 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
                 annotationsKey={this.annotationKey}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 focus={this.props.focus}
                 isSelected={this.props.isSelected}
                 isAnnotationOverlay={true}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 25ceb6218..36e687f71 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils";
+import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -648,6 +648,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelWidth}
                 PanelWidth={this.panelHeight}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 dropAction={"alias"}
                 VisibleHeight={this.visibleHeight}
                 focus={this.props.focus}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 5f766c121..c87ac719c 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast } from '../new_fields/Doc';
 import { Docs } from '../client/documents/Documents';
 import { CollectionView } from '../client/views/collections/CollectionView';
 import { DocumentView } from '../client/views/nodes/DocumentView';
-import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
+import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, returnZero } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck, faLongArrowAltLeft } from '@fortawesome/free-solid-svg-icons';
@@ -129,6 +129,8 @@ export default class MobileInterface extends React.Component {
                 onClick={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 PanelWidth={() => window.screen.width}
                 PanelHeight={() => window.screen.height}
                 renderDepth={0}
@@ -173,6 +175,8 @@ export default class MobileInterface extends React.Component {
         e.stopPropagation();
     }
 
+    panelHeight = () => window.innerHeight;
+    panelWidth = () => window.innerWidth;
     renderInkingContent = () => {
         console.log("rendering inking content");
         // TODO: support panning and zooming
@@ -201,8 +205,10 @@ export default class MobileInterface extends React.Component {
                         bringToFront={emptyFunction}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        PanelHeight={() => window.innerHeight}
-                        PanelWidth={() => window.innerWidth}
+                        PanelWidth={this.panelWidth}
+                        PanelHeight={this.panelHeight}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         focus={emptyFunction}
                         isSelected={returnFalse}
                         select={emptyFunction}
@@ -289,6 +295,8 @@ export default class MobileInterface extends React.Component {
                         onClick={undefined}
                         ScreenToLocalTransform={Transform.Identity}
                         ContentScaling={returnOne}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         PanelWidth={() => window.screen.width}
                         PanelHeight={() => window.screen.height}
                         renderDepth={0}
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 1a5a471a0..ff02596f7 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -28,7 +28,7 @@ export const documentSchema = createSchema({
     _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
     _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
     _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
-    _freezeOnDrop: "boolean",    // whether a document without native dimensions should have its width/height frozen as native dimensions on drop.  Used by Timeline view to make sure documents are scaled to fit the display thumbnail
+    _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From bc23e69a88234c213e444b1aa3c5eb895c35aca9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 6 Apr 2020 00:23:19 -0400
Subject: many more fixes to nativewidth/height layout changes.

---
 .../views/collections/CollectionSchemaView.tsx     | 51 ++++++++++++----------
 .../views/collections/CollectionStackingView.tsx   | 14 +++---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  7 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 19 +++++---
 .../CollectionMulticolumnView.tsx                  |  5 +++
 .../CollectionMultirowView.tsx                     |  6 ++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 .../views/nodes/ContentFittingDocumentView.tsx     | 17 ++++----
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 src/client/views/nodes/FormattedTextBox.scss       |  9 ++--
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 src/new_fields/util.ts                             |  2 +-
 14 files changed, 82 insertions(+), 63 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index df9f65a21..e835811c9 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -12,7 +12,7 @@ import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
@@ -118,29 +118,32 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     @computed
     get previewPanel() {
-        return !this.previewDocument ? (null) : <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
-            <ContentFittingDocumentView
-                Document={this.previewDocument}
-                DataDocument={undefined}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
-                focus={emptyFunction}
-                LibraryPath={this.props.LibraryPath}
-                renderDepth={this.props.renderDepth}
-                rootSelected={this.rootSelected}
-                PanelWidth={this.previewWidth}
-                PanelHeight={this.previewHeight}
-                getTransform={this.getPreviewTransform}
-                CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
-                CollectionView={this.props.CollectionView}
-                moveDocument={this.props.moveDocument}
-                addDocument={this.props.addDocument}
-                removeDocument={this.props.removeDocument}
-                active={this.props.active}
-                whenActiveChanged={this.props.whenActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
-            />
+        return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
+            {!this.previewDocument ? (null) :
+                <ContentFittingDocumentView
+                    Document={this.previewDocument}
+                    DataDocument={undefined}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
+                    fitToBox={true}
+                    FreezeDimensions={true}
+                    focus={emptyFunction}
+                    LibraryPath={this.props.LibraryPath}
+                    renderDepth={this.props.renderDepth}
+                    rootSelected={this.rootSelected}
+                    PanelWidth={this.previewWidth}
+                    PanelHeight={this.previewHeight}
+                    getTransform={this.getPreviewTransform}
+                    CollectionDoc={this.props.CollectionView?.props.Document}
+                    CollectionView={this.props.CollectionView}
+                    moveDocument={this.props.moveDocument}
+                    addDocument={this.props.addDocument}
+                    removeDocument={this.props.removeDocument}
+                    active={this.props.active}
+                    whenActiveChanged={this.props.whenActiveChanged}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={this.props.pinToPres}
+                />}
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8ceeb66f1..d17b1ee48 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -116,7 +116,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     componentDidMount() {
         super.componentDidMount();
         this._heightDisposer = reaction(() => {
-            if (this.props.Document._autoHeight && !this.props.NativeHeight()) {
+            if (this.props.Document._autoHeight && !this.layoutDoc._nativeHeight) {
                 const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
                 if (this.isStackingView) {
                     const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
@@ -175,17 +175,19 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
-            FreezeDimensions={this.props.freezeDimensions}
+            FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
-            fitToBox={this.props.fitToBox}
+            PanelWidth={width}
+            PanelHeight={height}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
-            PanelWidth={width}
-            PanelHeight={height}
             ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
+            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
             ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32449de5e..a46bc67a2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -43,7 +43,7 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
-    freezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
+    freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
     overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
     ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
     isAnnotationOverlay?: boolean;
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index d9a10cdc8..4b005ba42 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -72,7 +72,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
-            <CollectionFreeFormView {...this.props} freezeDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 10da50e35..eff4758cf 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -103,7 +103,7 @@ class TreeView extends React.Component<TreeViewProps> {
     set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
     @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
     @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
-    @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
+    @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
     @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
     @computed get fieldKey() {
         const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'");
@@ -280,7 +280,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
     docWidth = () => {
         const layoutDoc = Doc.Layout(this.props.document);
-        const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth);
+        const aspect = NumCast(layoutDoc._nativeHeight, 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.panelWidth() - 20));
         return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
     }
@@ -288,7 +288,7 @@ class TreeView extends React.Component<TreeViewProps> {
         const layoutDoc = Doc.Layout(this.props.document);
         const bounds = this.boundsOfCollectionDocument;
         return Math.min(this.MAX_EMBED_HEIGHT, (() => {
-            const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth, 1);
+            const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
             if (aspect) return this.docWidth() * aspect;
             if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
             return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
@@ -376,6 +376,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     rootSelected={returnTrue}
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
+                    FreezeDimensions={true}
                     PanelWidth={this.docWidth}
                     PanelHeight={this.docHeight}
                     getTransform={this.docTransform}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ceec9dfcc..22dd2d831 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -89,7 +89,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
-    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
+    @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
@@ -822,10 +822,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
+            NativeHeight: returnZero,
+            NativeWidth: returnZero,
+            fitToBox: false,
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
-            FreezeDimensions: this.props.freezeDimensions,
+            FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
             rootSelected: this.rootSelected,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
@@ -958,8 +961,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
-                    fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined}
-                    FreezeDimensions={this.props.layoutEngine !== undefined}
+                    fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
+                    FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
                 bounds: this.childDataProvider(pair.layout)
             }));
@@ -1122,8 +1125,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get contentScaling() {
         if (this.props.annotationsKey) return 0;
-        const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
-        const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+        const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
+        const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
+        const hscale = nh ? this.props.PanelHeight() / nh : 1;
+        const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
     render() {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index aa8e1fb43..7e511ae34 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -14,6 +14,7 @@ import "./collectionMulticolumnView.scss";
 import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
 import { List } from '../../../../new_fields/List';
+import { returnZero } from '../../../../Utils';
 
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
@@ -208,6 +209,10 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             {...this.props}
             Document={layout}
             DataDocument={layout.resolvedDataDoc as Doc}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
             backgroundColor={this.props.backgroundColor}
             CollectionDoc={this.props.Document}
             PanelWidth={width}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 5e59f8237..daf1fda6c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -6,7 +6,7 @@ import * as React from "react";
 import { Doc } from '../../../../new_fields/Doc';
 import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types';
 import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
-import { Utils } from '../../../../Utils';
+import { Utils, returnZero } from '../../../../Utils';
 import "./collectionMultirowView.scss";
 import { computed, trace, observable, action } from 'mobx';
 import { Transform } from '../../../util/Transform';
@@ -208,6 +208,10 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             {...this.props}
             Document={layout}
             DataDocument={layout.resolvedDataDoc as Doc}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
             backgroundColor={this.props.backgroundColor}
             CollectionDoc={this.props.Document}
             PanelWidth={width}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e4c8bbd7b..7e9e654cd 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -10,7 +10,6 @@ import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
 import { PositionDocument } from "../../../new_fields/documentSchemas";
 import { TraceMobx } from "../../../new_fields/util";
-import { returnFalse } from "../../../Utils";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@@ -108,7 +107,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     getTransform={this.getTransform}
                     NativeHeight={this.NativeHeight}
                     NativeWidth={this.NativeWidth}
-                    active={returnFalse}
+                    active={this.props.parentActive}
                     focus={this.focusDoc}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 52e5ed1dd..f66ff8020 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { computed } from "mobx";
 import { observer } from "mobx-react";
 import "react-table/react-table.css";
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
@@ -15,7 +15,7 @@ import "./ContentFittingDocumentView.scss";
 import { dropActionType } from "../../util/DragManager";
 
 interface ContentFittingDocumentViewProps {
-    Document?: Doc;
+    Document: Doc;
     DataDocument?: Doc;
     LayoutDoc?: () => Opt<Doc>;
     NativeWidth?: () => number;
@@ -48,13 +48,14 @@ interface ContentFittingDocumentViewProps {
 @observer
 export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
-    private get layoutDoc() { return this.props.Document && (this.props.LayoutDoc?.() || Doc.Layout(this.props.Document)); }
-    private nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || this.props.PanelWidth());
-    private nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || this.props.PanelHeight());
+    private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions; }
+    nativeWidth = () => { return NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())); }
+    nativeHeight = () => { return NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())); }
     @computed get scaling() {
-        const wscale = this.props.PanelWidth() / (this.nativeWidth() || this.props.PanelWidth() || 1);
+        const wscale = this.props.PanelWidth() / this.nativeWidth();
         if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
-            return (this.props.PanelHeight() / (this.nativeHeight() || this.props.PanelHeight() || 1)) || 1;
+            return (this.props.PanelHeight() / this.nativeHeight()) || 1;
         }
         return wscale || 1;
     }
@@ -67,7 +68,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
     private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
-    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
     private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
 
     @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d800579a1..32c5a02e5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -110,7 +110,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
-    @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+    get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@@ -124,8 +124,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _firstX: number = -1;
     private _firstY: number = -1;
 
-
-
     handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         this.removeMoveListeners();
         this.removeEndListeners();
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 526939438..7d40b3149 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -39,11 +39,6 @@
         position: absolute;
     }
 }
-
-.collectionfreeformview-container {
-    position: relative;
-}
-
 .formattedTextBox-outer {
     position: relative;
     overflow: auto;
@@ -75,6 +70,10 @@
     position: absolute;
     right: 0;
 
+    .collectionfreeformview-container {
+        position: relative;
+    }
+    
     >.formattedTextBox-sidebar-handle {
         right: unset;
         left: -5;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 052251d94..e0b3a6809 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -433,12 +433,14 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     contentFunc = () => [this.content];
     render() {
         TraceMobx();
+        const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
+        const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
         const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
                 width: this.props.PanelWidth() ? `${this.props.PanelWidth()}px` : `${100 / this.props.ContentScaling()}%`,
-                height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
+                height: this.props.PanelWidth() ? `${this.props.PanelWidth() * aspect}px` : `${100 / this.props.ContentScaling()}%`,
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined,
                 borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
             }} >
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 480a55da0..8c719ccd8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = true;
+const tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From 892174ef482f3d45167a0c2d1c3163e6b9443c66 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 7 Apr 2020 11:52:25 -0400
Subject: got rid of some run-time errors/warnings related to CORS-ifying
 imagebox requests, and avoiding memoizing messages for computedFn's outside
 reactions.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/SelectionManager.ts                |  8 ++-
 src/client/views/DocComponent.tsx                  |  8 +--
 src/client/views/GestureOverlay.tsx                |  1 -
 src/client/views/TemplateMenu.tsx                  | 66 +++++++++++-----------
 src/client/views/collections/CollectionSubView.tsx |  6 +-
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../views/collections/ParentDocumentSelector.tsx   |  2 -
 .../views/nodes/ContentFittingDocumentView.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  6 +-
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 .../views/presentationview/PresElementBox.tsx      |  2 +-
 src/new_fields/Doc.ts                              |  2 +-
 src/new_fields/ScriptField.ts                      |  5 +-
 15 files changed, 57 insertions(+), 59 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c9d7fac33..cc85e5c6f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -504,7 +504,7 @@ export namespace Docs {
                 const extension = path.extname(target);
                 target = `${target.substring(0, target.length - extension.length)}_o${extension}`;
             }
-            requestImageSize(target)
+            requestImageSize(Utils.CorsProxy(target))
                 .then((size: any) => {
                     const aspect = size.height / size.width;
                     if (!inst._nativeWidth) {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 2a6e3bf8a..6c386d684 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -55,11 +55,13 @@ export namespace SelectionManager {
         manager.SelectDoc(docView, ctrlPressed);
     }
 
-
+    // computed functions, such as used in IsSelected generate errors if they're called outside of a
+    // reaction context.  Specifying the context with 'outsideReaction' allows an efficiency feature
+    // to avoid unnecessary mobx invalidations when running inside a reaction.
     export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean {
         return outsideReaction ?
-            manager.SelectedDocuments.get(doc) ? true : false :
-            computedFn(function isSelected(doc: DocumentView) {
+            manager.SelectedDocuments.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
+            computedFn(function isSelected(doc: DocumentView) {  // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
                 return manager.SelectedDocuments.get(doc) ? true : false;
             })(doc);
     }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9456895d6..21eec66be 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -31,7 +31,7 @@ interface DocExtendableProps {
     fieldKey: string;
     isSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
-    rootSelected: () => boolean;
+    rootSelected: (outsideReaction?: boolean) => boolean;
 }
 export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
@@ -39,7 +39,7 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
         @computed get Document(): T { return schemaCtor(this.props.Document); }
         @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
         @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Cast(this.props.Document.resolvedDataDoc, Doc, null) || Doc.GetProto(this.props.Document)) as Doc; }
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected()) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -54,7 +54,7 @@ export interface DocAnnotatableProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     isSelected: (outsideReaction?: boolean) => boolean;
-    rootSelected: () => boolean;
+    rootSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
 }
 export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
@@ -92,7 +92,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            ((this.props.Document.forceActive && this.props.rootSelected()) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index f0205133c..69aa8dbaa 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -738,7 +738,6 @@ export default class GestureOverlay extends Touchable {
     }
 
     render() {
-        trace();
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index c72f3681a..8fb8e7516 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -127,44 +127,42 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(this)`));
             this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
                 <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
-            templateMenu.push(
-                <CollectionTreeView
-                    Document={Doc.UserDoc().templateDocs as Doc}
-                    CollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    ContainingCollectionView={undefined}
-                    rootSelected={returnFalse}
-                    onCheckedClick={this.scriptField!}
-                    onChildClick={this.scriptField!}
-                    LibraryPath={emptyPath}
-                    dropAction={undefined}
-                    active={returnTrue}
-                    ContentScaling={returnOne}
-                    bringToFront={emptyFunction}
-                    focus={emptyFunction}
-                    whenActiveChanged={emptyFunction}
-                    ScreenToLocalTransform={Transform.Identity}
-                    isSelected={returnFalse}
-                    pinToPres={emptyFunction}
-                    select={emptyFunction}
-                    renderDepth={1}
-                    addDocTab={returnFalse}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
-                    PanelWidth={this.return100}
-                    PanelHeight={this.return100}
-                    treeViewHideHeaderFields={true}
-                    annotationsKey={""}
-                    dontRegisterView={true}
-                    fieldKey={"data"}
-                    moveDocument={(doc: Doc) => false}
-                    removeDocument={(doc: Doc) => false}
-                    addDocument={(doc: Doc) => false} />
-            );
         }
         return <ul className="template-list" style={{ display: "block" }}>
             <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />
             {templateMenu}
+            <CollectionTreeView
+                Document={Doc.UserDoc().templateDocs as Doc}
+                CollectionView={undefined}
+                ContainingCollectionDoc={undefined}
+                ContainingCollectionView={undefined}
+                rootSelected={returnFalse}
+                onCheckedClick={this.scriptField!}
+                onChildClick={this.scriptField!}
+                LibraryPath={emptyPath}
+                dropAction={undefined}
+                active={returnTrue}
+                ContentScaling={returnOne}
+                bringToFront={emptyFunction}
+                focus={emptyFunction}
+                whenActiveChanged={emptyFunction}
+                ScreenToLocalTransform={Transform.Identity}
+                isSelected={returnFalse}
+                pinToPres={emptyFunction}
+                select={emptyFunction}
+                renderDepth={1}
+                addDocTab={returnFalse}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                PanelWidth={this.return100}
+                PanelHeight={this.return100}
+                treeViewHideHeaderFields={true}
+                annotationsKey={""}
+                dontRegisterView={true}
+                fieldKey={"data"}
+                moveDocument={(doc: Doc) => false}
+                removeDocument={(doc: Doc) => false}
+                addDocument={(doc: Doc) => false} />
         </ul>;
     }
 }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a46bc67a2..746b2e174 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,7 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelHeight: () => number;
     VisibleHeight?: () => number;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
-    rootSelected: () => boolean;
+    rootSelected: (outsideReaction?: boolean) => boolean;
     fieldKey: string;
     NativeWidth: () => number;
     NativeHeight: () => number;
@@ -99,8 +99,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
-        rootSelected = () => {
-            return this.props.isSelected() || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected() : false);
+        rootSelected = (outsideReaction: boolean) => {
+            return this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected(outsideReaction) : false);
         }
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d7224fa4b..c7ab66c9f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -122,7 +122,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected() && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
 
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 35e3a8958..afe269ec3 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -107,7 +107,6 @@ export class DockingViewButtonSelector extends React.Component<{ views: Document
     }
 
     @computed get flyout() {
-        trace();
         return (
             <div className="ParentDocumentSelector-flyout" title=" ">
                 <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
@@ -116,7 +115,6 @@ export class DockingViewButtonSelector extends React.Component<{ views: Document
     }
 
     render() {
-        trace();
         return <span title="Tap for menu, drag tab as document" onPointerDown={e => { this.props.views[0].select(false); e.stopPropagation(); }} className="buttonSelector">
             <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
                 <FontAwesomeIcon icon={"cog"} size={"sm"} />
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 642264b85..641797cac 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -42,7 +42,7 @@ interface ContentFittingDocumentViewProps {
     addDocTab: (document: Doc, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     dontRegisterView?: boolean;
-    rootSelected: () => boolean;
+    rootSelected: (outsideReaction?: boolean) => boolean;
 }
 
 @observer
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f374b5803..2df5c9bbd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -64,7 +64,7 @@ export interface DocumentViewProps {
     LayoutDoc?: () => Opt<Doc>;
     LibraryPath: Doc[];
     fitToBox?: boolean;
-    rootSelected: () => boolean; // whether the root of a template has been selected
+    rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
     onClick?: ScriptField;
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
@@ -964,8 +964,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const fallback = Cast(this.props.Document.layoutKey, "string");
         return typeof fallback === "string" ? fallback : "layout";
     }
-    rootSelected = () => {
-        return this.isSelected(false) || (this.props.Document.forceActive && this.props.rootSelected?.() ? true : false);
+    rootSelected = (outsideReaction: boolean) => {
+        return this.isSelected(outsideReaction) || (this.props.Document.forceActive && this.props.rootSelected?.(outsideReaction) ? true : false);
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     panelWidth = () => this.props.PanelWidth();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index dfcb03985..a3790d38b 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,7 +29,7 @@ export interface FieldViewProps {
     dropAction: dropActionType;
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
-    rootSelected: () => boolean;
+    rootSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
     addDocument?: (document: Doc) => boolean;
     addDocTab: (document: Doc, where: string) => boolean;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 36e687f71..720f09fe0 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -59,7 +59,7 @@ interface IViewerProps {
     PanelHeight: () => number;
     ContentScaling: () => number;
     select: (isCtrlPressed: boolean) => void;
-    rootSelected: () => boolean;
+    rootSelected: (outsideReaction?: boolean) => boolean;
     startupLive: boolean;
     renderDepth: number;
     focus: (doc: Doc) => void;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 7ad3474e8..289d3a9a1 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -197,7 +197,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
         const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree;
         const className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : "");
         const pbi = "presElementBox-interaction";
-        return !this.presElementDoc ? (null) : (
+        return !(this.presElementDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : (
             <div className={className} key={this.props.Document[Id] + this.indexInPres}
                 style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }}
                 onClick={e => { this.props.focus(this.presElementDoc); e.stopPropagation(); }}>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 964b2e316..d381447c5 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -713,7 +713,7 @@ export namespace Doc {
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
     export function IsBrushed(doc: Doc) {
         return computedFn(function IsBrushed(doc: Doc) {
-            return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(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)
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 148886848..4a3119aeb 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -131,7 +131,8 @@ export class ScriptField extends ObjectField {
 export class ComputedField extends ScriptField {
     _lastComputedResult: any;
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
-    value = computedFn((doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result);
+    value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
+    _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result;
     public static MakeScript(script: string, params: object = {}) {
         const compiled = ScriptField.CompileScript(script, params, false);
         return compiled.compiled ? new ComputedField(compiled) : undefined;
@@ -166,7 +167,7 @@ export namespace ComputedField {
     export function initPlugin() {
         Plugins.addGetterPlugin((doc, _, value) => {
             if (useComputed && value instanceof ComputedField) {
-                return { value: value.value(doc), shouldReturn: true };
+                return { value: value._valueOutsideReaction(doc), shouldReturn: true };
             }
         });
     }
-- 
cgit v1.2.3-70-g09d2


From e262d9ac73af5b2cef384468c47d69917e205d44 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 7 Apr 2020 23:01:46 -0400
Subject: lots of code cleanup - removed all northstar db stuff.  added
 scriptingBox.  renamed things.  made collectiontypes strings not numbers.

---
 package-lock.json                                  |    8 +-
 .../apis/google_docs/GoogleApiClientUtils.ts       |    2 +-
 src/client/apis/youtube/YoutubeBox.tsx             |    6 +-
 src/client/documents/DocumentTypes.ts              |   61 +-
 src/client/documents/Documents.ts                  |  108 +-
 src/client/northstar/core/BaseObject.ts            |   12 -
 .../northstar/core/attribute/AttributeModel.ts     |  111 -
 .../core/attribute/AttributeTransformationModel.ts |   52 -
 .../core/attribute/CalculatedAttributeModel.ts     |   42 -
 .../northstar/core/brusher/IBaseBrushable.ts       |   13 -
 src/client/northstar/core/brusher/IBaseBrusher.ts  |   11 -
 src/client/northstar/core/filter/FilterModel.ts    |   83 -
 src/client/northstar/core/filter/FilterOperand.ts  |    5 -
 src/client/northstar/core/filter/FilterType.ts     |    6 -
 .../northstar/core/filter/IBaseFilterConsumer.ts   |   12 -
 .../northstar/core/filter/IBaseFilterProvider.ts   |    8 -
 .../northstar/core/filter/ValueComparision.ts      |   78 -
 src/client/northstar/dash-fields/HistogramField.ts |   66 -
 .../dash-nodes/HistogramBinPrimitiveCollection.ts  |  240 -
 src/client/northstar/dash-nodes/HistogramBox.scss  |   40 -
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  175 -
 .../dash-nodes/HistogramBoxPrimitives.scss         |   42 -
 .../dash-nodes/HistogramBoxPrimitives.tsx          |  122 -
 .../dash-nodes/HistogramLabelPrimitives.scss       |   13 -
 .../dash-nodes/HistogramLabelPrimitives.tsx        |   80 -
 src/client/northstar/manager/Gateway.ts            |  299 -
 src/client/northstar/model/ModelExtensions.ts      |   48 -
 src/client/northstar/model/ModelHelpers.ts         |  220 -
 .../model/binRanges/AlphabeticVisualBinRange.ts    |   52 -
 .../model/binRanges/DateTimeVisualBinRange.ts      |  105 -
 .../model/binRanges/NominalVisualBinRange.ts       |   52 -
 .../model/binRanges/QuantitativeVisualBinRange.ts  |   90 -
 .../northstar/model/binRanges/VisualBinRange.ts    |   32 -
 .../model/binRanges/VisualBinRangeHelper.ts        |   70 -
 .../northstar/model/idea/MetricTypeMapping.ts      |   30 -
 src/client/northstar/model/idea/idea.ts            | 8557 --------------------
 src/client/northstar/operations/BaseOperation.ts   |  162 -
 .../northstar/operations/HistogramOperation.ts     |  158 -
 src/client/northstar/utils/ArrayUtil.ts            |   90 -
 src/client/northstar/utils/Extensions.ts           |   29 -
 src/client/northstar/utils/GeometryUtil.ts         |  133 -
 src/client/northstar/utils/IDisposable.ts          |    3 -
 src/client/northstar/utils/IEquatable.ts           |    3 -
 src/client/northstar/utils/KeyCodes.ts             |  137 -
 src/client/northstar/utils/LABColor.ts             |   90 -
 src/client/northstar/utils/MathUtil.ts             |  249 -
 src/client/northstar/utils/PartialClass.ts         |    7 -
 src/client/northstar/utils/SizeConverter.ts        |  101 -
 src/client/northstar/utils/StyleContants.ts        |   95 -
 src/client/northstar/utils/Utils.ts                |   75 -
 src/client/util/DictationManager.ts                |    6 +-
 src/client/util/DropConverter.ts                   |    2 +-
 src/client/util/KeyCodes.ts                        |  136 +
 src/client/util/type_decls.d                       |    1 -
 src/client/views/DocComponent.tsx                  |    2 +-
 src/client/views/DocumentDecorations.tsx           |    2 +-
 src/client/views/MainView.tsx                      |    6 +-
 src/client/views/ScriptBox.tsx                     |    2 +-
 src/client/views/SearchDocBox.tsx                  |    2 +-
 .../views/collections/CollectionSchemaCells.tsx    |    5 +-
 .../views/collections/CollectionSchemaView.tsx     |   22 -
 .../views/collections/CollectionStackingView.tsx   |    2 +-
 src/client/views/collections/CollectionSubView.tsx |    2 +-
 src/client/views/collections/CollectionView.tsx    |   52 +-
 .../views/collections/CollectionViewChromes.tsx    |    7 +-
 .../views/collections/ParentDocumentSelector.tsx   |    2 +-
 .../CollectionFreeFormLinkView.tsx                 |   10 +-
 .../CollectionFreeFormLinksView.tsx                |   56 -
 src/client/views/nodes/AudioBox.scss               |    4 +-
 src/client/views/nodes/ButtonBox.scss              |   38 -
 src/client/views/nodes/ButtonBox.tsx               |   97 -
 src/client/views/nodes/DocuLinkBox.scss            |   29 -
 src/client/views/nodes/DocuLinkBox.tsx             |  146 -
 src/client/views/nodes/DocumentBox.tsx             |    8 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   14 +-
 src/client/views/nodes/DocumentView.scss           |    4 +-
 src/client/views/nodes/DocumentView.tsx            |   40 +-
 src/client/views/nodes/FormattedTextBox.tsx        |    4 +-
 src/client/views/nodes/LabelBox.scss               |   38 +
 src/client/views/nodes/LabelBox.tsx                |   97 +
 src/client/views/nodes/LinkAnchorBox.scss          |   29 +
 src/client/views/nodes/LinkAnchorBox.tsx           |  146 +
 src/client/views/nodes/PDFBox.tsx                  |    3 +-
 src/client/views/nodes/PresBox.tsx                 |   10 +-
 src/client/views/nodes/ScriptingBox.scss           |    0
 src/client/views/nodes/ScriptingBox.tsx            |   71 +
 src/client/views/nodes/VideoBox.tsx                |    2 +-
 src/client/views/pdf/PDFViewer.tsx                 |    2 +-
 src/client/views/search/FilterBox.tsx              |    2 +-
 src/client/views/search/IconBar.tsx                |    2 +-
 src/client/views/search/IconButton.tsx             |    8 +-
 src/client/views/search/SearchBox.tsx              |    2 +-
 src/client/views/search/SearchItem.tsx             |    9 +-
 src/new_fields/Doc.ts                              |    6 +-
 src/new_fields/RichTextField.ts                    |    3 -
 src/new_fields/ScriptField.ts                      |    5 +-
 src/new_fields/documentSchemas.ts                  |    2 +-
 src/server/Message.ts                              |    2 +-
 src/server/RouteManager.ts                         |    1 -
 src/server/Websocket/Websocket.ts                  |    3 +-
 .../authentication/models/current_user_utils.ts    |   73 -
 src/server/updateProtos.ts                         |    3 +-
 102 files changed, 695 insertions(+), 12808 deletions(-)
 delete mode 100644 src/client/northstar/core/BaseObject.ts
 delete mode 100644 src/client/northstar/core/attribute/AttributeModel.ts
 delete mode 100644 src/client/northstar/core/attribute/AttributeTransformationModel.ts
 delete mode 100644 src/client/northstar/core/attribute/CalculatedAttributeModel.ts
 delete mode 100644 src/client/northstar/core/brusher/IBaseBrushable.ts
 delete mode 100644 src/client/northstar/core/brusher/IBaseBrusher.ts
 delete mode 100644 src/client/northstar/core/filter/FilterModel.ts
 delete mode 100644 src/client/northstar/core/filter/FilterOperand.ts
 delete mode 100644 src/client/northstar/core/filter/FilterType.ts
 delete mode 100644 src/client/northstar/core/filter/IBaseFilterConsumer.ts
 delete mode 100644 src/client/northstar/core/filter/IBaseFilterProvider.ts
 delete mode 100644 src/client/northstar/core/filter/ValueComparision.ts
 delete mode 100644 src/client/northstar/dash-fields/HistogramField.ts
 delete mode 100644 src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
 delete mode 100644 src/client/northstar/dash-nodes/HistogramBox.scss
 delete mode 100644 src/client/northstar/dash-nodes/HistogramBox.tsx
 delete mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
 delete mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
 delete mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
 delete mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
 delete mode 100644 src/client/northstar/manager/Gateway.ts
 delete mode 100644 src/client/northstar/model/ModelExtensions.ts
 delete mode 100644 src/client/northstar/model/ModelHelpers.ts
 delete mode 100644 src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
 delete mode 100644 src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
 delete mode 100644 src/client/northstar/model/binRanges/NominalVisualBinRange.ts
 delete mode 100644 src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
 delete mode 100644 src/client/northstar/model/binRanges/VisualBinRange.ts
 delete mode 100644 src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
 delete mode 100644 src/client/northstar/model/idea/MetricTypeMapping.ts
 delete mode 100644 src/client/northstar/model/idea/idea.ts
 delete mode 100644 src/client/northstar/operations/BaseOperation.ts
 delete mode 100644 src/client/northstar/operations/HistogramOperation.ts
 delete mode 100644 src/client/northstar/utils/ArrayUtil.ts
 delete mode 100644 src/client/northstar/utils/Extensions.ts
 delete mode 100644 src/client/northstar/utils/GeometryUtil.ts
 delete mode 100644 src/client/northstar/utils/IDisposable.ts
 delete mode 100644 src/client/northstar/utils/IEquatable.ts
 delete mode 100644 src/client/northstar/utils/KeyCodes.ts
 delete mode 100644 src/client/northstar/utils/LABColor.ts
 delete mode 100644 src/client/northstar/utils/MathUtil.ts
 delete mode 100644 src/client/northstar/utils/PartialClass.ts
 delete mode 100644 src/client/northstar/utils/SizeConverter.ts
 delete mode 100644 src/client/northstar/utils/StyleContants.ts
 delete mode 100644 src/client/northstar/utils/Utils.ts
 create mode 100644 src/client/util/KeyCodes.ts
 delete mode 100644 src/client/views/nodes/ButtonBox.scss
 delete mode 100644 src/client/views/nodes/ButtonBox.tsx
 delete mode 100644 src/client/views/nodes/DocuLinkBox.scss
 delete mode 100644 src/client/views/nodes/DocuLinkBox.tsx
 create mode 100644 src/client/views/nodes/LabelBox.scss
 create mode 100644 src/client/views/nodes/LabelBox.tsx
 create mode 100644 src/client/views/nodes/LinkAnchorBox.scss
 create mode 100644 src/client/views/nodes/LinkAnchorBox.tsx
 create mode 100644 src/client/views/nodes/ScriptingBox.scss
 create mode 100644 src/client/views/nodes/ScriptingBox.tsx

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

diff --git a/package-lock.json b/package-lock.json
index 2cd3c0b82..1949a8a5c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -750,7 +750,7 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
                 "@types/express": "*"
@@ -14376,7 +14376,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -15990,7 +15990,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -18303,7 +18303,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 0d44ee8e0..fa67ddbef 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -97,7 +97,7 @@ export namespace GoogleApiClientUtils {
                 const paragraphs = extractParagraphs(document);
                 let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => run as docs_v1.Schema$TextRun).join("")).join("");
                 text = text.substring(0, text.length - 1);
-                removeNewlines && text.ReplaceAll("\n", "");
+                removeNewlines && text.replace(/\n/g, "");
                 return { text, paragraphs };
             };
 
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index c940bba43..4b4145fcc 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -171,9 +171,9 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
      * in the title of the videos.
      */
     filterYoutubeTitleResult = (resultTitle: string) => {
-        let processedTitle: string = resultTitle.ReplaceAll("&amp;", "&");
-        processedTitle = processedTitle.ReplaceAll("&#39;", "'");
-        processedTitle = processedTitle.ReplaceAll("&quot;", "\"");
+        let processedTitle: string = resultTitle.replace(/&amp;/g, "&");//.ReplaceAll("&amp;", "&");
+        processedTitle = processedTitle.replace(/"&#39;/g, "'");
+        processedTitle = processedTitle.replace(/&quot;/g, "\"");
         return processedTitle;
     }
 
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index b6a6cc75a..ab32d7301 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -1,31 +1,36 @@
 export enum DocumentType {
     NONE = "none",
-    TEXT = "text",
-    HIST = "histogram",
-    IMG = "image",
-    WEB = "web",
-    COL = "collection",
-    KVP = "kvp",
-    VID = "video",
-    AUDIO = "audio",
-    PDF = "pdf",
-    IMPORT = "import",
-    LINK = "link",
-    LINKDB = "linkdb",
-    BUTTON = "button",
-    SLIDER = "slider",
-    YOUTUBE = "youtube",
-    WEBCAM = "webcam",
-    FONTICON = "fonticonbox",
-    PRES = "presentation",
-    RECOMMENDATION = "recommendation",
-    LINKFOLLOW = "linkfollow",
-    PRESELEMENT = "preselement",
-    QUERY = "query",
-    COLOR = "color",
-    DOCULINK = "doculink",
-    PDFANNO = "pdfanno",
-    INK = "ink",
-    DOCUMENT = "document",
-    SCREENSHOT = "screenshot",
+
+    // core data types
+    RTF = "rtf",      // rich text
+    IMG = "image",      // image box
+    WEB = "web",        // web page or html clipping
+    COL = "collection", // collection
+    KVP = "kvp",        // key value pane
+    VID = "video",      // video
+    AUDIO = "audio",    // audio
+    PDF = "pdf",        // pdf
+    INK = "ink",        // ink stroke
+    SCREENSHOT = "screenshot",  // view of a desktop application
+    FONTICON = "fonticonbox",   // font icon
+    QUERY = "query",            // search query
+    LABEL = "label",            // simple text label
+    WEBCAM = "webcam",          // webcam
+    PDFANNO = "pdfanno",        // pdf text selection (could be just a collection?)
+    DATE = "date",              // calendar view of a date
+    SCRIPTING = "script",       // script editor
+
+    // special purpose wrappers that either take no data or are compositions of lower level types
+    LINK = "link",              // link  (view of a document that acts as a link)
+    LINKANCHOR = "linkanchor",  // blue dot link anchor  (view of a link document's anchor)
+    IMPORT = "import",          // directory import box  (file system directory)
+    SLIDER = "slider",          // number slider  (view of a number)
+    PRES = "presentation",      // presentation   (view of a collection) --- shouldn't this be a view type?  technically requires a special view in which documents must have their aliasOf fields filled in
+    PRESELEMENT = "preselement",// presentation item (view of a document in a collection)
+    COLOR = "color",            // color picker (view of a color picker for a color string)
+    YOUTUBE = "youtube",        // youtube directory (view of you tube search results)
+    DOCHOLDER = "docholder",    // nested document (view of a document)
+
+    LINKDB = "linkdb",          // database of links  ??? why do we have this
+    RECOMMENDATION = "recommendation", // view of a recommendation
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cca0e7bc6..b71205001 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,3 @@
-import { HistogramField } from "../northstar/dash-fields/HistogramField";
-import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
-import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 import { CollectionView } from "../views/collections/CollectionView";
 import { CollectionViewType } from "../views/collections/CollectionView";
 import { AudioBox } from "../views/nodes/AudioBox";
@@ -8,21 +5,16 @@ import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
 import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
+import { ScriptingBox } from "../views/nodes/ScriptingBox";
 import { VideoBox } from "../views/nodes/VideoBox";
 import { WebBox } from "../views/nodes/WebBox";
-import { Gateway } from "../northstar/manager/Gateway";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-import { action } from "mobx";
-import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
-import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
-import { AggregateFunction } from "../northstar/model/idea/idea";
 import { OmitKeys, JSONUtils, Utils } from "../../Utils";
 import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../new_fields/Doc";
 import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField";
 import { HtmlField } from "../../new_fields/HtmlField";
 import { List } from "../../new_fields/List";
 import { Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
@@ -32,7 +24,7 @@ import { LinkManager } from "../util/LinkManager";
 import { DocumentManager } from "../util/DocumentManager";
 import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox";
 import { Scripting } from "../util/Scripting";
-import { ButtonBox } from "../views/nodes/ButtonBox";
+import { LabelBox } from "../views/nodes/LabelBox";
 import { SliderBox } from "../views/nodes/SliderBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
@@ -41,16 +33,12 @@ import { ComputedField, ScriptField } from "../../new_fields/ScriptField";
 import { ProxyField } from "../../new_fields/Proxy";
 import { DocumentType } from "./DocumentTypes";
 import { RecommendationsBox } from "../views/RecommendationsBox";
-import { SearchBox } from "../views/search/SearchBox";
-
-//import { PresBox } from "../views/nodes/PresBox";
-//import { PresField } from "../../new_fields/PresField";
 import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
-import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
-import { DocumentBox } from "../views/nodes/DocumentBox";
+import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox";
+import { DocHolderBox } from "../views/nodes/DocumentBox";
 import { InkingStroke } from "../views/InkingStroke";
 import { InkField } from "../../new_fields/InkField";
 import { InkingControl } from "../views/InkingControl";
@@ -80,7 +68,7 @@ export interface DocumentOptions {
     _showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
     _scrollTop?: number; // scroll location for pdfs
     _chromeStatus?: string;
-    _viewType?: number;
+    _viewType?: string; // sub type of a collection
     _gridGap?: number; // gap between items in masonry view
     _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
     _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
@@ -156,6 +144,7 @@ export interface DocumentOptions {
     treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
     isFacetFilter?: boolean; // whether document functions as a facet filter in a tree view
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
+    editScriptOnClick?: string; // script field key to edit when document is clicked (e.g., "onClick", "onChecked")
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
     textTransform?: string; // is linear view expanded
@@ -192,14 +181,10 @@ export namespace Docs {
         const data = "data";
 
         const TemplateMap: TemplateMap = new Map([
-            [DocumentType.TEXT, {
+            [DocumentType.RTF, {
                 layout: { view: FormattedTextBox, dataField: "text" },
                 options: { _height: 150, _xMargin: 10, _yMargin: 10 }
             }],
-            [DocumentType.HIST, {
-                layout: { view: HistogramBox, dataField: data },
-                options: { _height: 300, backgroundColor: "black" }
-            }],
             [DocumentType.QUERY, {
                 layout: { view: QueryBox, dataField: data },
                 options: { _width: 400 }
@@ -224,8 +209,8 @@ export namespace Docs {
                 layout: { view: KeyValueBox, dataField: data },
                 options: { _height: 150 }
             }],
-            [DocumentType.DOCUMENT, {
-                layout: { view: DocumentBox, dataField: data },
+            [DocumentType.DOCHOLDER, {
+                layout: { view: DocHolderBox, dataField: data },
                 options: { _height: 250 }
             }],
             [DocumentType.VID, {
@@ -253,11 +238,14 @@ export namespace Docs {
                 layout: { view: EmptyBox, dataField: data },
                 options: { childDropAction: "alias", title: "LINK DB" }
             }],
+            [DocumentType.SCRIPTING, {
+                layout: { view: ScriptingBox, dataField: data }
+            }],
             [DocumentType.YOUTUBE, {
                 layout: { view: YoutubeBox, dataField: data }
             }],
-            [DocumentType.BUTTON, {
-                layout: { view: ButtonBox, dataField: data },
+            [DocumentType.LABEL, {
+                layout: { view: LabelBox, dataField: data },
             }],
             [DocumentType.SLIDER, {
                 layout: { view: SliderBox, dataField: data },
@@ -521,6 +509,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options);
         }
 
+        export function ScriptingDocument(url: string, options: DocumentOptions = {}) {
+            return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), new YoutubeField(new URL(url)), options);
+        }
+
         export function VideoDocument(url: string, options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(new URL(url)), options);
         }
@@ -543,10 +535,6 @@ export namespace Docs {
             return instance;
         }
 
-        export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.HIST), new HistogramField(histoOp), options);
-        }
-
         export function QueryDocument(options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.QUERY), "", options);
         }
@@ -556,7 +544,7 @@ export namespace Docs {
         }
 
         export function TextDocument(text: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options, undefined, "text");
+            return InstanceFromProto(Prototypes.get(DocumentType.RTF), text, options, undefined, "text");
         }
 
         export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
@@ -566,8 +554,8 @@ export namespace Docs {
             linkDocProto.anchor2 = target.doc;
 
             if (linkDocProto.layout_key1 === undefined) {
-                Cast(linkDocProto.proto, Doc, null).layout_key1 = DocuLinkBox.LayoutString("anchor1");
-                Cast(linkDocProto.proto, Doc, null).layout_key2 = DocuLinkBox.LayoutString("anchor2");
+                Cast(linkDocProto.proto, Doc, null).layout_key1 = LinkAnchorBox.LayoutString("anchor1");
+                Cast(linkDocProto.proto, Doc, null).layout_key2 = LinkAnchorBox.LayoutString("anchor2");
                 Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
                 Cast(linkDocProto.proto, Doc, null).layoutKey = undefined;
             }
@@ -605,37 +593,6 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), options);
         }
 
-        export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) {
-            const schemaName = options.title ? options.title : "-no schema-";
-            const ctlog = await Gateway.Instance.GetSchema(url, schemaName);
-            if (ctlog && ctlog.schemas) {
-                const schema = ctlog.schemas[0];
-                const schemaDoc = Docs.Create.TreeDocument([], { ...options, _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: schema.displayName! });
-                const schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
-                if (!schemaDocuments) {
-                    return;
-                }
-                CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc);
-                const docs = schemaDocuments;
-                CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                    DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
-                        if (field instanceof Doc) {
-                            docs.push(field);
-                        } else {
-                            const atmod = new ColumnAttributeModel(attr);
-                            const histoOp = new HistogramOperation(schema.displayName!,
-                                new AttributeTransformationModel(atmod, AggregateFunction.None),
-                                new AttributeTransformationModel(atmod, AggregateFunction.Count),
-                                new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                            docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, _width: 200, _height: 200, title: attr.displayName! }));
-                        }
-                    }));
-                });
-                return schemaDoc;
-            }
-            return Docs.Create.TreeDocument([], { _width: 50, _height: 100, title: schemaName });
-        }
-
         export function WebDocument(url: string, options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(new URL(url)), options);
         }
@@ -649,7 +606,7 @@ export namespace Docs {
         }
 
         export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.DOCUMENT), document, { title: document ? document.title + "" : "container", ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", ...options });
         }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -688,8 +645,12 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry });
         }
 
+        export function LabelDocument(options?: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.LABEL), undefined, { ...(options || {}) });
+        }
+
         export function ButtonDocument(options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) });
+            return InstanceFromProto(Prototypes.get(DocumentType.LABEL), undefined, { ...(options || {}), editScriptOnClick: "onClick" });
         }
 
         export function SliderDocument(options?: DocumentOptions) {
@@ -842,9 +803,6 @@ export namespace Docs {
             } else if (field instanceof AudioField) {
                 created = Docs.Create.AudioDocument((field).url.href, resolved);
                 layout = AudioBox.LayoutString;
-            } else if (field instanceof HistogramField) {
-                created = Docs.Create.HistogramDocument((field).HistoOp, resolved);
-                layout = HistogramBox.LayoutString;
             } else if (field instanceof InkField) {
                 const { selectedColor, selectedWidth, selectedTool } = InkingControl.Instance;
                 created = Docs.Create.InkDocument(selectedColor, selectedTool, Number(selectedWidth), (field).inkData, resolved);
@@ -856,9 +814,11 @@ export namespace Docs {
                 created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
                 layout = FormattedTextBox.LayoutString;
             }
-            created.layout = layout?.(fieldKey);
-            created.title = fieldKey;
-            proto && (created.proto = Doc.GetProto(proto));
+            if (created) {
+                created.layout = layout?.(fieldKey);
+                created.title = fieldKey;
+                proto && created.proto && (created.proto = Doc.GetProto(proto));
+            }
             return created;
         }
 
@@ -881,10 +841,6 @@ export namespace Docs {
                 if (!options._width) options._width = 400;
                 if (!options._height) options._height = options._width * 1200 / 927;
             }
-            if (type.indexOf("excel") !== -1) {
-                ctor = Docs.Create.DBDocument;
-                options.dropAction = "copy";
-            }
             if (type.indexOf("html") !== -1) {
                 if (path.includes(window.location.hostname)) {
                     const s = path.split('/');
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
deleted file mode 100644
index ed3818071..000000000
--- a/src/client/northstar/core/BaseObject.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { IEquatable } from '../utils/IEquatable';
-import { IDisposable } from '../utils/IDisposable';
-
-export class BaseObject implements IEquatable, IDisposable {
-
-    public Equals(other: Object): boolean {
-        return this === other;
-    }
-
-    public Dispose(): void {
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
deleted file mode 100644
index c89b1617c..000000000
--- a/src/client/northstar/core/attribute/AttributeModel.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea';
-import { BaseObject } from '../BaseObject';
-import { observable } from "mobx";
-
-export abstract class AttributeModel extends BaseObject {
-    public abstract get DisplayName(): string;
-    public abstract get CodeName(): string;
-    public abstract get DataType(): DataType;
-    public abstract get VisualizationHints(): VisualizationHint[];
-}
-
-export class ColumnAttributeModel extends AttributeModel {
-    public Attribute: Attribute;
-
-    constructor(attribute: Attribute) {
-        super();
-        this.Attribute = attribute;
-    }
-
-    public get DataType(): DataType {
-        return this.Attribute.dataType ? this.Attribute.dataType : DataType.Undefined;
-    }
-
-    public get DisplayName(): string {
-        return this.Attribute.displayName ? this.Attribute.displayName.ReplaceAll("_", " ") : "";
-    }
-
-    public get CodeName(): string {
-        return this.Attribute.rawName ? this.Attribute.rawName : "";
-    }
-
-    public get VisualizationHints(): VisualizationHint[] {
-        return this.Attribute.visualizationHints ? this.Attribute.visualizationHints : [];
-    }
-
-    public Equals(other: ColumnAttributeModel): boolean {
-        return this.Attribute.rawName === other.Attribute.rawName;
-    }
-}
-
-export class CodeAttributeModel extends AttributeModel {
-    private _visualizationHints: VisualizationHint[];
-
-    public CodeName: string;
-
-    @observable
-    public Code: string;
-
-    constructor(code: string, codeName: string, displayName: string, visualizationHints: VisualizationHint[]) {
-        super();
-        this.Code = code;
-        this.CodeName = codeName;
-        this.DisplayName = displayName;
-        this._visualizationHints = visualizationHints;
-    }
-
-    public get DataType(): DataType {
-        return DataType.Undefined;
-    }
-
-    @observable
-    public DisplayName: string;
-
-    public get VisualizationHints(): VisualizationHint[] {
-        return this._visualizationHints;
-    }
-
-    public Equals(other: CodeAttributeModel): boolean {
-        return this.CodeName === other.CodeName;
-    }
-
-}
-
-export class BackendAttributeModel extends AttributeModel {
-    private _dataType: DataType;
-    private _displayName: string;
-    private _codeName: string;
-    private _visualizationHints: VisualizationHint[];
-
-    public Id: string;
-
-    constructor(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]) {
-        super();
-        this.Id = id;
-        this._dataType = dataType;
-        this._displayName = displayName;
-        this._codeName = codeName;
-        this._visualizationHints = visualizationHints;
-    }
-
-    public get DataType(): DataType {
-        return this._dataType;
-    }
-
-    public get DisplayName(): string {
-        return this._displayName.ReplaceAll("_", " ");
-    }
-
-    public get CodeName(): string {
-        return this._codeName;
-    }
-
-    public get VisualizationHints(): VisualizationHint[] {
-        return this._visualizationHints;
-    }
-
-    public Equals(other: BackendAttributeModel): boolean {
-        return this.Id === other.Id;
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
deleted file mode 100644
index 66485183b..000000000
--- a/src/client/northstar/core/attribute/AttributeTransformationModel.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-
-import { computed, observable } from "mobx";
-import { AggregateFunction } from "../../model/idea/idea";
-import { AttributeModel } from "./AttributeModel";
-import { IEquatable } from "../../utils/IEquatable";
-
-export class AttributeTransformationModel implements IEquatable {
-
-    @observable public AggregateFunction: AggregateFunction;
-    @observable public AttributeModel: AttributeModel;
-
-    constructor(attributeModel: AttributeModel, aggregateFunction: AggregateFunction = AggregateFunction.None) {
-        this.AttributeModel = attributeModel;
-        this.AggregateFunction = aggregateFunction;
-    }
-
-    @computed
-    public get PresentedName(): string {
-        var displayName = this.AttributeModel.DisplayName;
-        if (this.AggregateFunction === AggregateFunction.Count) {
-            return "count";
-        }
-        if (this.AggregateFunction === AggregateFunction.Avg) {
-            displayName = "avg(" + displayName + ")";
-        }
-        else if (this.AggregateFunction === AggregateFunction.Max) {
-            displayName = "max(" + displayName + ")";
- }
-        else if (this.AggregateFunction === AggregateFunction.Min) {
-            displayName = "min(" + displayName + ")";
- }
-        else if (this.AggregateFunction === AggregateFunction.Sum) {
-            displayName = "sum(" + displayName + ")";
- }
-        else if (this.AggregateFunction === AggregateFunction.SumE) {
-            displayName = "sumE(" + displayName + ")";
- }
-
-        return displayName;
-    }
-
-    public clone(): AttributeTransformationModel {
-        var clone = new AttributeTransformationModel(this.AttributeModel);
-        clone.AggregateFunction = this.AggregateFunction;
-        return clone;
-    }
-
-    public Equals(other: AttributeTransformationModel): boolean {
-        return this.AggregateFunction === other.AggregateFunction &&
-            this.AttributeModel.Equals(other.AttributeModel);
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
deleted file mode 100644
index a197c1305..000000000
--- a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel";
-import { DataType, VisualizationHint } from '../../model/idea/idea';
-
-export class CalculatedAttributeManager {
-    public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>();
-
-    public static Clear() {
-        this.AllCalculatedAttributes = new Array<AttributeModel>();
-    }
-
-    public static CreateBackendAttributeModel(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]): BackendAttributeModel {
-        var filtered = this.AllCalculatedAttributes.filter(am => {
-            if (am instanceof BackendAttributeModel &&
-                am.Id === id) {
-                return true;
-            }
-            return false;
-        });
-        if (filtered.length > 0) {
-            return filtered[0] as BackendAttributeModel;
-        }
-        var newAttr = new BackendAttributeModel(id, dataType, displayName, codeName, visualizationHints);
-        this.AllCalculatedAttributes.push(newAttr);
-        return newAttr;
-    }
-
-    public static CreateCodeAttributeModel(code: string, codeName: string, visualizationHints: VisualizationHint[]): CodeAttributeModel {
-        var filtered = this.AllCalculatedAttributes.filter(am => {
-            if (am instanceof CodeAttributeModel &&
-                am.CodeName === codeName) {
-                return true;
-            }
-            return false;
-        });
-        if (filtered.length > 0) {
-            return filtered[0] as CodeAttributeModel;
-        }
-        var newAttr = new CodeAttributeModel(code, codeName, codeName.ReplaceAll("_", " "), visualizationHints);
-        this.AllCalculatedAttributes.push(newAttr);
-        return newAttr;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
deleted file mode 100644
index 87f4ba413..000000000
--- a/src/client/northstar/core/brusher/IBaseBrushable.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { PIXIPoint } from '../../utils/MathUtil';
-import { IEquatable } from '../../utils/IEquatable';
-import { Doc } from '../../../../new_fields/Doc';
-
-export interface IBaseBrushable<T> extends IEquatable {
-    BrusherModels: Array<Doc>;
-    BrushColors: Array<number>;
-    Position: PIXIPoint;
-    Size: PIXIPoint;
-}
-export function instanceOfIBaseBrushable<T>(object: any): object is IBaseBrushable<T> {
-    return 'BrusherModels' in object;
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts
deleted file mode 100644
index d2de6ed62..000000000
--- a/src/client/northstar/core/brusher/IBaseBrusher.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { PIXIPoint } from '../../utils/MathUtil';
-import { IEquatable } from '../../utils/IEquatable';
-
-
-export interface IBaseBrusher<T> extends IEquatable {
-    Position: PIXIPoint;
-    Size: PIXIPoint;
-}
-export function instanceOfIBaseBrusher<T>(object: any): object is IBaseBrusher<T> {
-    return 'BrushableModels' in object;
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
deleted file mode 100644
index 6ab96b33d..000000000
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { ValueComparison } from "./ValueComparision";
-import { Utils } from "../../utils/Utils";
-import { IBaseFilterProvider } from "./IBaseFilterProvider";
-import { FilterOperand } from "./FilterOperand";
-import { HistogramField } from "../../dash-fields/HistogramField";
-import { Cast, FieldValue } from "../../../../new_fields/Types";
-import { Doc } from "../../../../new_fields/Doc";
-
-export class FilterModel {
-    public ValueComparisons: ValueComparison[];
-    constructor() {
-        this.ValueComparisons = new Array<ValueComparison>();
-    }
-
-    public Equals(other: FilterModel): boolean {
-        if (!Utils.EqualityHelper(this, other)) return false;
-        if (!this.isSame(this.ValueComparisons, (other).ValueComparisons)) return false;
-        return true;
-    }
-
-    private isSame(a: ValueComparison[], b: ValueComparison[]): boolean {
-        if (a.length !== b.length) {
-            return false;
-        }
-        for (let i = 0; i < a.length; i++) {
-            let valueComp = a[i];
-            if (!valueComp.Equals(b[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public ToPythonString(): string {
-        return "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
-    }
-
-    public static And(filters: string[]): string {
-        let ret = filters.filter(f => f !== "").join(" && ");
-        return ret;
-    }
-    public static GetFilterModelsRecursive(baseOperation: IBaseFilterProvider, visitedFilterProviders: Set<IBaseFilterProvider>, filterModels: FilterModel[], isFirst: boolean): string {
-        let ret = "";
-        visitedFilterProviders.add(baseOperation);
-        let filtered = baseOperation.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0);
-        if (!isFirst && filtered.length > 0) {
-            filterModels.push(...filtered);
-            ret = "(" + baseOperation.FilterModels.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
-        }
-        if (Utils.isBaseFilterConsumer(baseOperation) && baseOperation.Links) {
-            let children = new Array<string>();
-            let linkedGraphNodes = baseOperation.Links;
-            linkedGraphNodes.map(linkVm => {
-                let filterDoc = FieldValue(Cast(linkVm.linkedFrom, Doc));
-                if (filterDoc) {
-                    let filterHistogram = Cast(filterDoc.data, HistogramField);
-                    if (filterHistogram) {
-                        if (!visitedFilterProviders.has(filterHistogram.HistoOp)) {
-                            let child = FilterModel.GetFilterModelsRecursive(filterHistogram.HistoOp, visitedFilterProviders, filterModels, false);
-                            if (child !== "") {
-                                // if (linkVm.IsInverted) {
-                                //     child = "! " + child;
-                                // }
-                                children.push(child);
-                            }
-                        }
-                    }
-                }
-            });
-
-            let childrenJoined = children.join(baseOperation.FilterOperand === FilterOperand.AND ? " && " : " || ");
-            if (children.length > 0) {
-                if (ret !== "") {
-                    ret = "(" + ret + " &&  (" + childrenJoined + "))";
-                }
-                else {
-                    ret = "(" + childrenJoined + ")";
-                }
-            }
-        }
-        return ret;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterOperand.ts b/src/client/northstar/core/filter/FilterOperand.ts
deleted file mode 100644
index 2e8e8d6a0..000000000
--- a/src/client/northstar/core/filter/FilterOperand.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export enum FilterOperand
-{
-    AND,
-    OR
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterType.ts b/src/client/northstar/core/filter/FilterType.ts
deleted file mode 100644
index 9adbc087f..000000000
--- a/src/client/northstar/core/filter/FilterType.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum FilterType
-{
-    Filter,
-    Brush,
-    Slice
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
deleted file mode 100644
index e7549d113..000000000
--- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { FilterOperand } from '../filter/FilterOperand';
-import { IEquatable } from '../../utils/IEquatable';
-import { Doc } from '../../../../new_fields/Doc';
-
-export interface IBaseFilterConsumer extends IEquatable {
-    FilterOperand: FilterOperand;
-    Links: Doc[];
-}
-
-export function instanceOfIBaseFilterConsumer(object: any): object is IBaseFilterConsumer {
-    return 'FilterOperand' in object;
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts
deleted file mode 100644
index fc3301b11..000000000
--- a/src/client/northstar/core/filter/IBaseFilterProvider.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { FilterModel } from '../filter/FilterModel';
-
-export interface IBaseFilterProvider {
-    FilterModels: Array<FilterModel>;
-}
-export function instanceOfIBaseFilterProvider(object: any): object is IBaseFilterProvider {
-    return 'FilterModels' in object;
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
deleted file mode 100644
index 65687a82b..000000000
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Predicate } from '../../model/idea/idea';
-import { Utils } from '../../utils/Utils';
-import { AttributeModel } from '../attribute/AttributeModel';
-
-export class ValueComparison {
-
-    public attributeModel: AttributeModel;
-    public Value: any;
-    public Predicate: Predicate;
-
-    public constructor(attributeModel: AttributeModel, predicate: Predicate, value: any) {
-        this.attributeModel = attributeModel;
-        this.Value = value;
-        this.Predicate = predicate;
-    }
-
-    public Equals(other: Object): boolean {
-        if (!Utils.EqualityHelper(this, other)) {
-            return false;
-        }
-        if (this.Predicate !== (other as ValueComparison).Predicate) {
-            return false;
-        }
-        let isComplex = (typeof this.Value === "object");
-        if (!isComplex && this.Value !== (other as ValueComparison).Value) {
-            return false;
-        }
-        if (isComplex && !this.Value.Equals((other as ValueComparison).Value)) {
-            return false;
-        }
-        return true;
-    }
-
-    public ToPythonString(): string {
-        var op = "";
-        switch (this.Predicate) {
-            case Predicate.EQUALS:
-                op = "==";
-                break;
-            case Predicate.GREATER_THAN:
-                op = ">";
-                break;
-            case Predicate.GREATER_THAN_EQUAL:
-                op = ">=";
-                break;
-            case Predicate.LESS_THAN:
-                op = "<";
-                break;
-            case Predicate.LESS_THAN_EQUAL:
-                op = "<=";
-                break;
-            default:
-                op = "==";
-                break;
-        }
-
-        var val = this.Value.toString();
-        if (typeof this.Value === 'string' || this.Value instanceof String) {
-            val = "\"" + val + "\"";
-        }
-        var ret = " ";
-        var rawName = this.attributeModel.CodeName;
-        switch (this.Predicate) {
-            case Predicate.STARTS_WITH:
-                ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
-                return ret;
-            case Predicate.ENDS_WITH:
-                ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
-                return ret;
-            case Predicate.CONTAINS:
-                ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
-                return ret;
-            default:
-                ret += rawName + " " + op + " " + val + " ";
-                return ret;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
deleted file mode 100644
index 076516977..000000000
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { observable } from "mobx";
-import { custom, serializable } from "serializr";
-import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
-import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
-import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { OmitKeys } from "../../../Utils";
-import { Deserializable } from "../../util/SerializationHelper";
-import { Copy, ToScriptString, ToString } from "../../../new_fields/FieldSymbols";
-
-function serialize(field: HistogramField) {
-    const obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
-    return obj;
-}
-
-function deserialize(jp: any) {
-    let X: AttributeTransformationModel | undefined;
-    let Y: AttributeTransformationModel | undefined;
-    let V: AttributeTransformationModel | undefined;
-
-    const schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
-    if (schema) {
-        CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-            if (attr.displayName === jp.X.AttributeModel.Attribute.DisplayName) {
-                X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
-            }
-            if (attr.displayName === jp.Y.AttributeModel.Attribute.DisplayName) {
-                Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
-            }
-            if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) {
-                V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
-            }
-        });
-        if (X && Y && V) {
-            return new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization);
-        }
-    }
-    return HistogramOperation.Empty;
-}
-
-@Deserializable("histogramField")
-export class HistogramField extends ObjectField {
-    @serializable(custom(serialize, deserialize)) @observable public readonly HistoOp: HistogramOperation;
-    constructor(data?: HistogramOperation) {
-        super();
-        this.HistoOp = data ? data : HistogramOperation.Empty;
-    }
-
-    toString(): string {
-        return JSON.stringify(OmitKeys(this.HistoOp, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit);
-    }
-
-    [Copy]() {
-        // const y = this.HistoOp;
-        // const z = this.HistoOp.Copy;
-        return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
-    }
-
-    [ToScriptString]() {
-        return this.toString();
-    }
-    [ToString]() {
-        return this.toString();
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
deleted file mode 100644
index 6b36ffc9e..000000000
--- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-import React = require("react");
-import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
-import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
-import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
-import { ModelHelpers } from "../../northstar/model/ModelHelpers";
-import { LABColor } from '../../northstar/utils/LABColor';
-import { PIXIRectangle } from "../../northstar/utils/MathUtil";
-import { StyleConstants } from "../../northstar/utils/StyleContants";
-import { HistogramBox } from "./HistogramBox";
-import "./HistogramBoxPrimitives.scss";
-
-export class HistogramBinPrimitive {
-    constructor(init?: Partial<HistogramBinPrimitive>) {
-        Object.assign(this, init);
-    }
-    public DataValue: number = 0;
-    public Rect: PIXIRectangle = PIXIRectangle.EMPTY;
-    public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY;
-    public MarginPercentage: number = 0;
-    public Color: number = StyleConstants.WARNING_COLOR;
-    public Opacity: number = 1;
-    public BrushIndex: number = 0;
-    public BarAxis: number = -1;
-}
-
-export class HistogramBinPrimitiveCollection {
-    private static TOLERANCE: number = 0.0001;
-
-    private _histoBox: HistogramBox;
-    private get histoOp() { return this._histoBox.HistoOp; }
-    private get histoResult() { return this.histoOp.Result as HistogramResult; }
-    private get sizeConverter() { return this._histoBox.SizeConverter; }
-    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
-    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
-
-    constructor(bin: Bin, histoBox: HistogramBox) {
-        this._histoBox = histoBox;
-        let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2
-
-        brushing.orderedBrushes.reduce((brushFactorSum, brush) => {
-            switch (histoBox.ChartType) {
-                case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
-                case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
-                case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush);
-                case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum);
-            }
-        }, 0);
-
-        // adjust brush rects (stacking or not)
-        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
-        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex !== allBrushIndex && b.DataValue !== 0.0);
-        filteredBinPrims.reduce((sum, fbp) => {
-            if (histoBox.ChartType === ChartType.VerticalBar) {
-                if (this.histoOp.Y.AggregateFunction === AggregateFunction.Count) {
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height);
-                    return sum + fbp.Rect.height;
-                }
-                if (this.histoOp.Y.AggregateFunction === AggregateFunction.Avg) {
-                    var w = fbp.Rect.width / 2.0;
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / filteredBinPrims.length, fbp.Rect.height);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
-                    return sum + fbp.Rect.width;
-                }
-            }
-            else if (histoBox.ChartType === ChartType.HorizontalBar) {
-                if (this.histoOp.X.AggregateFunction === AggregateFunction.Count) {
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
-                    return sum + fbp.Rect.width;
-                }
-                if (this.histoOp.X.AggregateFunction === AggregateFunction.Avg) {
-                    var h = fbp.Rect.height / 2.0;
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / filteredBinPrims.length);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
-                    return sum + fbp.Rect.height;
-                }
-            }
-            return 0;
-        }, 0);
-        this.BinPrimitives = this.BinPrimitives.reverse();
-        var f = this.BinPrimitives.filter(b => b.BrushIndex === allBrushIndex);
-        this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY;
-    }
-
-    private setupBrushing(bin: Bin, normalization: number) {
-        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
-        var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]];
-        this.histoResult.brushes!.map(brush => brush.brushIndex !== 0 && brush.brushIndex !== overlapBrushIndex && orderedBrushes.push(brush));
-        return {
-            orderedBrushes,
-            maxAxis: orderedBrushes.reduce((prev, Brush) => {
-                let aggResult = this.getBinValue(normalization, bin, Brush.brushIndex!);
-                return aggResult !== undefined && aggResult > prev ? aggResult : prev;
-            }, Number.MIN_VALUE)
-        };
-    }
-
-    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
-
-        let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
-        if (unNormalizedValue === undefined) {
-            return brushFactorSum;
-        }
-
-        var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
-            unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
-
-        let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult));
-
-        // bcz: are these calls needed?  
-        let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
-        let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
-
-        var returnBrushFactorSum = brushFactorSum;
-        if (allUnNormalizedValue !== undefined) {
-            var brushFactor = (unNormalizedValue / allUnNormalizedValue);
-            returnBrushFactorSum += brushFactor;
-            returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
-
-            var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo);
-            var ratio = (tempRect.width / tempRect.height);
-            var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum));
-            var newWidth = newHeight * ratio;
-
-            xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0);
-            yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0);
-            xTo = (xFrom + newWidth);
-            yFrom = (yTo + newHeight);
-        }
-        var alpha = 0.0;
-        var color = this.baseColorFromBrush(brush);
-        var lerpColor = LABColor.Lerp(
-            LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR),
-            LABColor.FromColor(color),
-            (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha)));
-        var dataColor = LABColor.ToColor(lerpColor);
-
-        this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
-        return returnBrushFactorSum;
-    }
-
-    private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
-        let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
-        if (unNormalizedValue !== undefined) {
-            let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
-
-            if (xFrom !== undefined && yFrom !== undefined && xTo !== undefined && yTo !== undefined) {
-                this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
-            }
-        }
-        return 0;
-    }
-
-    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
-        let dataValue = this.getBinValue(1, bin, brush.brushIndex!);
-        if (dataValue !== undefined) {
-            let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
-            let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
-
-            var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y);
-            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
-                this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
-                this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
-
-            this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization !== 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
-        }
-        return 0;
-    }
-
-    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
-        let dataValue = this.getBinValue(0, bin, brush.brushIndex!);
-        if (dataValue !== undefined) {
-            let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
-
-            var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X);
-            var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                yTo + (yFrom - yTo) / 2.0 - 1,
-                this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                2.0);
-
-            this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization !== 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
-        }
-        return 0;
-    }
-
-    public getBinValue(axis: number, bin: Bin, brushIndex: number) {
-        var aggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis === 0 ? this.histoOp.X : axis === 1 ? this.histoOp.Y : this.histoOp.V, this.histoResult, brushIndex);
-        let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
-        return dataValue !== null && dataValue.hasResult ? dataValue.result : undefined;
-    }
-
-    private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = axis.AggregateFunction;
-        var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams);
-        let aggResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey);
-        return aggResult instanceof MarginAggregateResult && aggResult.absolutMargin ? aggResult.absolutMargin : 0;
-    }
-
-    private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle,
-        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
-        var binPrimitive = new HistogramBinPrimitive(
-            {
-                Rect: new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo),
-                MarginRect: marginRect,
-                MarginPercentage: marginPercentage,
-                BrushIndex: brush.brushIndex,
-                Color: color,
-                Opacity: opacity,
-                DataValue: dataValue,
-                BarAxis: barAxis
-            });
-        this.BinPrimitives.push(binPrimitive);
-    }
-
-    private baseColorFromBrush(brush: Brush): number {
-        let bc = StyleConstants.BRUSH_COLORS;
-        if (brush.brushIndex === ModelHelpers.RestBrushIndex(this.histoResult)) {
-            return StyleConstants.HIGHLIGHT_COLOR;
-        }
-        else if (brush.brushIndex === ModelHelpers.OverlapBrushIndex(this.histoResult)) {
-            return StyleConstants.OVERLAP_COLOR;
-        }
-        else if (brush.brushIndex === ModelHelpers.AllBrushIndex(this.histoResult)) {
-            return 0x00ff00;
-        }
-        else if (bc.length > 0) {
-            return bc[brush.brushIndex! % bc.length];
-        }
-        // else if (this.histoOp.BrushColors.length > 0) {
-        //     return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
-        // }
-        return StyleConstants.HIGHLIGHT_COLOR;
-    }
-}
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
deleted file mode 100644
index 06d781263..000000000
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-.histogrambox-container {
-    padding: 0vw;
-    position: absolute;
-    top: -50%;
-    left:-50%;
-    text-align: center;
-    width: 100%;
-    height: 100%;
-    background: black; 
-  }
-  .histogrambox-xaxislabel {
-    position:absolute;
-    left:0;
-    width:100%;
-    text-align: center;
-    bottom:0;
-    background: lightgray;
-    font-size: 14;
-    font-weight: bold;
-  }
-  .histogrambox-yaxislabel {
-    position:absolute;
-    height:100%;
-    width: 25px;
-    left:0;
-    bottom:0;
-    background: lightgray;
-  }
-  .histogrambox-yaxislabel-text {
-    position:absolute;
-    left:0;
-    width: 1000px;
-    transform-origin: 10px 10px;
-    transform: rotate(-90deg);
-    text-align: left;
-    font-size: 14;
-    font-weight: bold;
-    bottom: calc(50% - 25px);
-  }
-  
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
deleted file mode 100644
index 8fee53fb9..000000000
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import React = require("react");
-import { action, computed, observable, reaction, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
-import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
-import { ModelHelpers } from "../../northstar/model/ModelHelpers";
-import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { SizeConverter } from "../../northstar/utils/SizeConverter";
-import { DragManager } from "../../util/DragManager";
-import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
-import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
-import { HistogramField } from "../dash-fields/HistogramField";
-import "../utils/Extensions";
-import "./HistogramBox.scss";
-import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
-import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
-import { StyleConstants } from "../utils/StyleContants";
-import { Cast } from "../../../new_fields/Types";
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
-
-
-@observer
-export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(HistogramBox, fieldStr); }
-    private _dropXRef = React.createRef<HTMLDivElement>();
-    private _dropYRef = React.createRef<HTMLDivElement>();
-    private _dropXDisposer?: DragManager.DragDropDisposer;
-    private _dropYDisposer?: DragManager.DragDropDisposer;
-
-    @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
-    @observable public VisualBinRanges: VisualBinRange[] = [];
-    @observable public ValueRange: number[] = [];
-    @computed public get HistogramResult(): HistogramResult { return this.HistoOp.Result as HistogramResult; }
-    @observable public SizeConverter: SizeConverter = new SizeConverter();
-
-    @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); }
-    @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; }
-    @computed get ChartType() {
-        return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ?
-            (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) :
-            this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
-    }
-
-    @action
-    dropX = (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField);
-            if (h) {
-                this.HistoOp.X = h.HistoOp.X;
-            }
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-    @action
-    dropY = (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField);
-            if (h) {
-                this.HistoOp.Y = h.HistoOp.X;
-            }
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-
-    @action
-    xLabelPointerDown = (e: React.PointerEvent) => {
-        this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
-    }
-    @action
-    yLabelPointerDown = (e: React.PointerEvent) => {
-        this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
-    }
-
-    componentDidMount() {
-        if (this._dropXRef.current) {
-            this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, this.dropX.bind(this));
-        }
-        if (this._dropYRef.current) {
-            this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, this.dropY.bind(this));
-        }
-        reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
-        reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
-        reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], (size: number[]) => this.SizeConverter.SetIsSmall(size[0] < 40 && size[1] < 40));
-        reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
-            (binRanges: BinRange[] | undefined) => {
-                if (binRanges) {
-                    this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) =>
-                        VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
-
-                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult, ModelHelpers.AllBrushIndex(this.HistogramResult));
-                    this.ValueRange = Object.values(this.HistogramResult.bins!).reduce((prev, cur) => {
-                        let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult;
-                        return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev;
-                    }, [Number.MAX_VALUE, Number.MIN_VALUE]);
-                }
-            });
-    }
-
-    componentWillUnmount() {
-        if (this._dropXDisposer) {
-            this._dropXDisposer();
-        }
-        if (this._dropYDisposer) {
-            this._dropYDisposer();
-        }
-    }
-
-    async activateHistogramOperation(catalog?: Catalog) {
-        if (catalog) {
-            let histoOp = await Cast(this.props.Document[this.props.fieldKey], HistogramField);
-            runInAction(() => {
-                this.HistoOp = histoOp ? histoOp.HistoOp : HistogramOperation.Empty;
-                if (this.HistoOp !== HistogramOperation.Empty) {
-                    reaction(() => DocListCast(this.props.Document.linkedFromDocs), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
-                    reaction(() => DocListCast(this.props.Document.brushingDocs).length,
-                        async () => {
-                            let brushingDocs = await DocListCastAsync(this.props.Document.brushingDocs);
-                            const proto = this.props.Document.proto;
-                            if (proto && brushingDocs) {
-                                let mapped = brushingDocs.map((brush, i) => {
-                                    brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
-                                    let brushed = DocListCast(brush.brushingDocs);
-                                    if (!brushed.length) return null;
-                                    return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
-                                });
-                                runInAction(() => this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...mapped.filter(m => m) as { l: Doc, b: Doc }[]));
-                            }
-                        }, { fireImmediately: true });
-                    reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
-                }
-            });
-        }
-    }
-
-    @action
-    private onScrollWheel = (e: React.WheelEvent) => {
-        this.HistoOp.DrillDown(e.deltaY > 0);
-        e.stopPropagation();
-    }
-
-    render() {
-        let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
-        let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
-        let loff = this.SizeConverter.LeftOffset;
-        let toff = this.SizeConverter.TopOffset;
-        let roff = this.SizeConverter.RightOffset;
-        let boff = this.SizeConverter.BottomOffset;
-        return (
-            <div className="histogrambox-container" onWheel={this.onScrollWheel}>
-                <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
-                    <span className="histogrambox-yaxislabel-text">
-                        {labelY}
-                    </span>
-                </div>
-                <div className="histogrambox-primitives" style={{
-                    transform: `translate(${loff + 25}px, ${toff}px)`,
-                    width: `calc(100% - ${loff + roff + 25}px)`,
-                    height: `calc(100% - ${toff + boff}px)`,
-                }}>
-                    <HistogramLabelPrimitives HistoBox={this} />
-                    <HistogramBoxPrimitives HistoBox={this} />
-                </div>
-                <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
-                    {labelX}
-                </div>
-            </div>
-        );
-    }
-}
-
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
deleted file mode 100644
index 26203612a..000000000
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-.histogramboxprimitives-container {
-    width: 100%;
-    height: 100%;
-}
-.histogramboxprimitives-border {
-    border: 3px;
-    pointer-events: none;
-    position: absolute;
-    fill:"transparent";
-    stroke: white;
-    stroke-width: 1px;
-}
-.histogramboxprimitives-bar {
-    position: absolute;
-    border: 1px;
-    border-style: solid;
-    border-color: #282828;
-    pointer-events: all;
-}
-
-.histogramboxprimitives-placer {
-    position: absolute;
-    pointer-events: none;
-    width: 100%;
-    height: 100%;
-}
-.histogramboxprimitives-svgContainer {
-    position: absolute;
-    top:0;
-    left:0;
-    width:100%;
-    height: 100%;
-}
-.histogramboxprimitives-line {
-    position: absolute;
-    background: darkGray;
-    stroke: darkGray;
-    stroke-width: 1px;
-    width:100%;
-    height:100%;
-    opacity: 0.4;
-}
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
deleted file mode 100644
index 66d91cc1d..000000000
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React = require("react");
-import { computed, observable, reaction, runInAction, trace, action } from "mobx";
-import { observer } from "mobx-react";
-import { Utils as DashUtils, emptyFunction } from '../../../Utils';
-import { FilterModel } from "../../northstar/core/filter/FilterModel";
-import { ModelHelpers } from "../../northstar/model/ModelHelpers";
-import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
-import { LABColor } from '../../northstar/utils/LABColor';
-import { PIXIRectangle } from "../../northstar/utils/MathUtil";
-import { StyleConstants } from "../../northstar/utils/StyleContants";
-import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection";
-import { HistogramBox } from "./HistogramBox";
-import "./HistogramBoxPrimitives.scss";
-
-export interface HistogramPrimitivesProps {
-    HistoBox: HistogramBox;
-}
-@observer
-export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> {
-    private get histoOp() { return this.props.HistoBox.HistoOp; }
-    private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
-    @observable _selectedPrims: HistogramBinPrimitive[] = [];
-    @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
-    @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
-    @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); }
-    @computed get barPrimitives() {
-        let histoResult = this.props.HistoBox.HistogramResult;
-        if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) {
-            return (null);
-        }
-        let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
-        return Object.keys(histoResult.bins).reduce((prims: JSX.Element[], key: string) => {
-            let drawPrims = new HistogramBinPrimitiveCollection(histoResult.bins![key], this.props.HistoBox);
-            let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
-                ModelHelpers.GetBinFilterModel(histoResult.bins![key], allBrushIndex, histoResult, this.histoOp.X, this.histoOp.Y));
-            drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
-                prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle))));
-            return prims;
-        }, [] as JSX.Element[]);
-    }
-
-    componentDidMount() {
-        reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0);
-    }
-
-    private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
-        let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
-        if (!rawAllBrushPrim) {
-            return emptyFunction;
-        }
-        let allBrushPrim = rawAllBrushPrim;
-        return () => runInAction(() => {
-            if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
-                this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim), 1);
-                this.histoOp.RemoveFilterModels([filterModel]);
-            }
-            else {
-                this._selectedPrims.push(allBrushPrim);
-                this.histoOp.AddFilterModels([filterModel]);
-            }
-        });
-    }
-
-    private renderGridLinesAndLabels(axis: number) {
-        if (!this.props.HistoBox.SizeConverter.Initialized) {
-            return (null);
-        }
-        let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels();
-        return <svg className="histogramboxprimitives-svgContainer">
-            {labels.reduce((prims, binLabel, i) => {
-                let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
-                prims.push(this.drawLine(r.xFrom, r.yFrom, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
-                if (i === labels.length - 1) {
-                    prims.push(this.drawLine(axis === 0 ? r.xTo : r.xFrom, axis === 0 ? r.yFrom : r.yTo, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
-                }
-                return prims;
-            }, [] as JSX.Element[])}
-        </svg>;
-    }
-
-    drawLine(xFrom: number, yFrom: number, width: number, height: number) {
-        if (height < 0) {
-            yFrom += height;
-            height = -height;
-        }
-        if (width < 0) {
-            xFrom += width;
-            width = -width;
-        }
-        let trans2Xpercent = `${(xFrom + width) / this.renderDimension * 100}%`;
-        let trans2Ypercent = `${(yFrom + height) / this.renderDimension * 100}%`;
-        let trans1Xpercent = `${xFrom / this.renderDimension * 100}%`;
-        let trans1Ypercent = `${yFrom / this.renderDimension * 100}%`;
-        return <line className="histogramboxprimitives-line" key={DashUtils.GenerateGuid()} x1={trans1Xpercent} x2={`${trans2Xpercent}`} y1={trans1Ypercent} y2={`${trans2Ypercent}`} />;
-    }
-    drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = emptyFunction) {
-        if (r.height < 0) {
-            r.y += r.height;
-            r.height = -r.height;
-        }
-        if (r.width < 0) {
-            r.x += r.width;
-            r.width = -r.width;
-        }
-        let transXpercent = `${r.x / this.renderDimension * 100}%`;
-        let transYpercent = `${r.y / this.renderDimension * 100}%`;
-        let widthXpercent = `${r.width / this.renderDimension * 100}%`;
-        let heightYpercent = `${r.height / this.renderDimension * 100}%`;
-        return (<rect className={`histogramboxprimitives-${classExt}`} key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler(); }}
-            x={transXpercent} width={`${widthXpercent}`} y={transYpercent} height={`${heightYpercent}`} fill={color ? `${LABColor.RGBtoHexString(color)}` : "transparent"} />);
-    }
-    render() {
-        return <div className="histogramboxprimitives-container">
-            {this.xaxislines}
-            {this.yaxislines}
-            <svg className="histogramboxprimitives-svgContainer">
-                {this.barPrimitives}
-                {this.selectedPrimitives}
-            </svg>
-        </div>;
-    }
-}
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
deleted file mode 100644
index 304d33771..000000000
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-
-  .histogramLabelPrimitives-gridlabel {
-    position:absolute;
-    transform-origin: left top;
-    font-size: 11;
-    color:white;
-  }
-  .histogramLabelPrimitives-placer {
-    position:absolute;
-    width:100%;
-    height:100%;
-    pointer-events: none;
-  }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
deleted file mode 100644
index 62aebd3c6..000000000
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import React = require("react");
-import { action, computed, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Utils as DashUtils } from '../../../Utils';
-import { NominalVisualBinRange } from "../model/binRanges/NominalVisualBinRange";
-import "../utils/Extensions";
-import { StyleConstants } from "../utils/StyleContants";
-import { HistogramBox } from "./HistogramBox";
-import "./HistogramLabelPrimitives.scss";
-import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
-
-@observer
-export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
-    componentDidMount() {
-        reaction(() => [this.props.HistoBox.props.PanelWidth(), this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
-            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
-    }
-
-    @action
-    static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) {
-        const textWidth = 30;
-        if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) {
-            let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length;
-            histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2)));
-        } else if (histoBox.SizeConverter.LabelAngle) {
-            histoBox.SizeConverter.SetLabelAngle(0);
-        }
-    }
-    @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
-    @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
-
-    private renderGridLinesAndLabels(axis: number) {
-        let sc = this.props.HistoBox.SizeConverter;
-        let vb = this.props.HistoBox.VisualBinRanges;
-        if (!vb.length || !sc.Initialized) {
-            return (null);
-        }
-        let dim = (axis === 0 ? this.props.HistoBox.props.PanelWidth() : this.props.HistoBox.props.PanelHeight()) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
-            (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
-            sc.MaxLabelSizes[axis].coords[axis] + 5);
-
-        let labels = vb[axis].GetLabels();
-        return labels.reduce((prims, binLabel, i) => {
-            let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
-            if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) {
-                const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "...");
-                const textHeight = 14; const textWidth = 30;
-                let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth);
-                let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
-
-                if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) {
-                    let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.props.PanelWidth();
-                    xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
-                }
-
-                let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
-                let yPercent = axis === 0 ? `${this.props.HistoBox.props.PanelHeight() - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
-
-                prims.push(
-                    <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
-                        <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis === 0 ? sc.LabelAngle : 0}rad)` }}>
-                            {label}
-                        </div>
-                    </div>
-                );
-            }
-            return prims;
-        }, [] as JSX.Element[]);
-    }
-
-    render() {
-        let xaxislines = this.xaxislines;
-        let yaxislines = this.yaxislines;
-        return <div className="histogramLabelPrimitives-container">
-            {xaxislines}
-            {yaxislines}
-        </div>;
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
deleted file mode 100644
index c541cce6a..000000000
--- a/src/client/northstar/manager/Gateway.ts
+++ /dev/null
@@ -1,299 +0,0 @@
-import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea";
-import { computed, observable, action } from "mobx";
-
-export class Gateway {
-
-    private static _instance: Gateway;
-
-    private constructor() {
-    }
-
-    public static get Instance() {
-        return this._instance || (this._instance = new this());
-    }
-
-    public async GetCatalog(): Promise<Catalog> {
-        try {
-            const json = await this.MakeGetRequest("catalog");
-            const cat = Catalog.fromJS(json);
-            return cat;
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async PostSchema(csvdata: string, schemaname: string): Promise<string> {
-        try {
-            const json = await this.MakePostJsonRequest("postSchema", { csv: csvdata, schema: schemaname });
-            // const cat = Catalog.fromJS(json);
-            // return cat;
-            return json;
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async GetSchema(pathname: string, schemaname: string): Promise<Catalog> {
-        try {
-            const json = await this.MakeGetRequest("schema", undefined, { path: pathname, schema: schemaname });
-            const cat = Catalog.fromJS(json);
-            return cat;
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async ClearCatalog(): Promise<void> {
-        try {
-            await this.MakePostJsonRequest("Datamart/ClearAllAugmentations", {});
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async TerminateServer(): Promise<void> {
-        try {
-            const url = Gateway.ConstructUrl("terminateServer");
-            const response = await fetch(url,
-                {
-                    redirect: "follow",
-                    method: "POST",
-                    credentials: "include"
-                });
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async Compile(data: any): Promise<CompileResults | undefined> {
-        const json = await this.MakePostJsonRequest("compile", data);
-        if (json !== null) {
-            const cr = CompileResults.fromJS(json);
-            return cr;
-        }
-    }
-
-    public async SubmitResult(data: any): Promise<void> {
-        try {
-            console.log(data);
-            const url = Gateway.ConstructUrl("submitProblem");
-            const response = await fetch(url,
-                {
-                    redirect: "follow",
-                    method: "POST",
-                    credentials: "include",
-                    body: JSON.stringify(data)
-                });
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async SpecifyProblem(data: any): Promise<void> {
-        try {
-            console.log(data);
-            const url = Gateway.ConstructUrl("specifyProblem");
-            const response = await fetch(url,
-                {
-                    redirect: "follow",
-                    method: "POST",
-                    credentials: "include",
-                    body: JSON.stringify(data)
-                });
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-    public async ExportToScript(solutionId: string): Promise<string> {
-        try {
-            const url = Gateway.ConstructUrl("exportsolution/script/" + solutionId);
-            const response = await fetch(url,
-                {
-                    redirect: "follow",
-                    method: "GET",
-                    credentials: "include"
-                });
-            return await response.text();
-        }
-        catch (error) {
-            throw new Error("can not reach northstar's backend");
-        }
-    }
-
-
-    public async StartOperation(data: any): Promise<OperationReference | undefined> {
-        const json = await this.MakePostJsonRequest("operation", data);
-        if (json !== null) {
-            const or = OperationReference.fromJS(json);
-            return or;
-        }
-    }
-
-    public async GetResult(data: any): Promise<Result | undefined> {
-        const json = await this.MakePostJsonRequest("result", data);
-        if (json !== null) {
-            const res = Result.fromJS(json);
-            return res;
-        }
-    }
-
-    public async PauseOperation(data: any): Promise<void> {
-        const url = Gateway.ConstructUrl("pause");
-        await fetch(url,
-            {
-                redirect: "follow",
-                method: "POST",
-                credentials: "include",
-                body: JSON.stringify(data)
-            });
-    }
-
-    public async MakeGetRequest(endpoint: string, signal?: AbortSignal, params?: any): Promise<any> {
-        let url = !params ? Gateway.ConstructUrl(endpoint) :
-            (() => {
-                let newUrl = new URL(Gateway.ConstructUrl(endpoint));
-                Object.getOwnPropertyNames(params).map(prop =>
-                    newUrl.searchParams.append(prop, params[prop]));
-                return Gateway.ConstructUrl(endpoint) + newUrl.search;
-            })();
-
-        const response = await fetch(url,
-            {
-                redirect: "follow",
-                method: "GET",
-                credentials: "include",
-                signal
-            });
-        const json = await response.json();
-        return json;
-    }
-
-    public async MakePostJsonRequest(endpoint: string, data: any, signal?: AbortSignal): Promise<any> {
-        const url = Gateway.ConstructUrl(endpoint);
-        const response = await fetch(url,
-            {
-                redirect: "follow",
-                method: "POST",
-                credentials: "include",
-                body: JSON.stringify(data),
-                signal
-            });
-        const json = await response.json();
-        return json;
-    }
-
-
-    public static ConstructUrl(appendix: string): string {
-        let base = NorthstarSettings.Instance.ServerUrl;
-        if (base.slice(-1) === "/") {
-            base = base.slice(0, -1);
-        }
-        let url = base + "/" + NorthstarSettings.Instance.ServerApiPath + "/" + appendix;
-        return url;
-    }
-}
-
-declare var ENV: any;
-
-export class NorthstarSettings {
-    private _environment: any;
-
-    @observable
-    public ServerUrl: string = document.URL;
-
-    @observable
-    public ServerApiPath?: string;
-
-    @observable
-    public SampleSize?: number;
-
-    @observable
-    public XBins?: number;
-
-    @observable
-    public YBins?: number;
-
-    @observable
-    public SplashTimeInMS?: number;
-
-    @observable
-    public ShowFpsCounter?: boolean;
-
-    @observable
-    public IsMenuFixed?: boolean;
-
-    @observable
-    public ShowShutdownButton?: boolean;
-
-    @observable
-    public IsDarpa?: boolean;
-
-    @observable
-    public IsIGT?: boolean;
-
-    @observable
-    public DegreeOfParallelism?: number;
-
-    @observable
-    public ShowWarnings?: boolean;
-
-    @computed
-    public get IsDev(): boolean {
-        return ENV.IsDev;
-    }
-
-    @computed
-    public get TestDataFolderPath(): string {
-        return this.Origin + "testdata/";
-    }
-
-    @computed
-    public get Origin(): string {
-        return window.location.origin + "/";
-    }
-
-    private static _instance: NorthstarSettings;
-
-    @action
-    public UpdateEnvironment(environment: any): void {
-        /*let serverParam = new URL(document.URL).searchParams.get("serverUrl");
-        if (serverParam) {
-            if (serverParam === "debug") {
-                this.ServerUrl = `http://${window.location.hostname}:1234`;
-            }
-            else {
-                this.ServerUrl = serverParam;
-            }
-        }
-        else {
-            this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL;
-        }*/
-        this.ServerUrl = environment.SERVER_URL ? environment.SERVER_URL : document.URL;
-        this.ServerApiPath = environment.SERVER_API_PATH;
-        this.SampleSize = environment.SAMPLE_SIZE;
-        this.XBins = environment.X_BINS;
-        this.YBins = environment.Y_BINS;
-        this.SplashTimeInMS = environment.SPLASH_TIME_IN_MS;
-        this.ShowFpsCounter = environment.SHOW_FPS_COUNTER;
-        this.ShowShutdownButton = environment.SHOW_SHUTDOWN_BUTTON;
-        this.IsMenuFixed = environment.IS_MENU_FIXED;
-        this.IsDarpa = environment.IS_DARPA;
-        this.IsIGT = environment.IS_IGT;
-        this.DegreeOfParallelism = environment.DEGREE_OF_PARALLISM;
-    }
-
-    public static get Instance(): NorthstarSettings {
-        if (!this._instance) {
-            this._instance = new NorthstarSettings();
-        }
-        return this._instance;
-    }
-}
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
deleted file mode 100644
index 29f80d2d1..000000000
--- a/src/client/northstar/model/ModelExtensions.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea';
-import { Utils } from '../utils/Utils';
-
-import { FilterModel } from '../core/filter/FilterModel';
-
-(SingleDimensionAggregateParameters as any).prototype.Equals = function (other: Object) {
-    if (!Utils.EqualityHelper(this, other)) return false;
-    if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
-        (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
-    if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
-    return true;
-};
-
-{
-    (AttributeParameters as any).prototype.Equals = function (other: AttributeParameters) {
-        return (this).constructor.name === (<any>other).constructor.name &&
-            this.rawName === other.rawName;
-    };
-}
-
-{
-    (Solution as any).prototype.Equals = function (other: Object) {
-        if (!Utils.EqualityHelper(this, other)) return false;
-        if ((this as Solution).solutionId !== (other as Solution).solutionId) return false;
-        return true;
-    };
-}
-
-{
-    (MarginAggregateParameters as any).prototype.Equals = function (other: Object) {
-        if (!Utils.EqualityHelper(this, other)) return false;
-        if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
-            (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
-        if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
-
-        if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
-        return true;
-    };
-}
-
-{
-    (Brush as any).prototype.Equals = function (other: Object) {
-        if (!Utils.EqualityHelper(this, other)) return false;
-        if ((this as Brush).brushEnum !== (other as Brush).brushEnum) return false;
-        if ((this as Brush).brushIndex !== (other as Brush).brushIndex) return false;
-        return true;
-    };
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
deleted file mode 100644
index 88e6e72b8..000000000
--- a/src/client/northstar/model/ModelHelpers.ts
+++ /dev/null
@@ -1,220 +0,0 @@
-
-import { action } from "mobx";
-import { AggregateFunction, AggregateKey, AggregateParameters, AttributeColumnParameters, AttributeParameters, AverageAggregateParameters, Bin, BinningParameters, Brush, BrushEnum, CountAggregateParameters, DataType, EquiWidthBinningParameters, HistogramResult, MarginAggregateParameters, SingleBinBinningParameters, SingleDimensionAggregateParameters, SumAggregateParameters, AggregateBinRange, NominalBinRange, AlphabeticBinRange, Predicate, Schema, Attribute, AttributeGroup, Exception, AttributeBackendParameters, AttributeCodeParameters } from '../model/idea/idea';
-import { ValueComparison } from "../core/filter/ValueComparision";
-import { ArrayUtil } from "../utils/ArrayUtil";
-import { AttributeModel, ColumnAttributeModel, BackendAttributeModel, CodeAttributeModel } from "../core/attribute/AttributeModel";
-import { FilterModel } from "../core/filter/FilterModel";
-import { AlphabeticVisualBinRange } from "./binRanges/AlphabeticVisualBinRange";
-import { NominalVisualBinRange } from "./binRanges/NominalVisualBinRange";
-import { VisualBinRangeHelper } from "./binRanges/VisualBinRangeHelper";
-import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-
-export class ModelHelpers {
-
-    public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult,
-        brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
-        {
-            if (aggParameters === undefined) {
-                aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm);
-            }
-            else {
-                aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            }
-            return new AggregateKey(
-                {
-                    aggregateParameterIndex: ModelHelpers.GetAggregateParametersIndex(histogramResult, aggParameters),
-                    brushIndex: brushIndex
-                });
-        }
-    }
-
-    public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number {
-        return Array.from(histogramResult.aggregateParameters!).findIndex((value, i, set) => {
-            if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters) return true;
-            if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters) return true;
-            if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters) return true;
-            return false;
-        });
-    }
-
-    public static GetAggregateParameter(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel): AggregateParameters | undefined {
-        var aggParam: AggregateParameters | undefined;
-        if (atm.AggregateFunction === AggregateFunction.Avg) {
-            var avg = new AverageAggregateParameters();
-            avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            avg.distinctAttributeParameters = distinctAttributeParameters;
-            aggParam = avg;
-        }
-        else if (atm.AggregateFunction === AggregateFunction.Count) {
-            var cnt = new CountAggregateParameters();
-            cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            cnt.distinctAttributeParameters = distinctAttributeParameters;
-            aggParam = cnt;
-        }
-        else if (atm.AggregateFunction === AggregateFunction.Sum) {
-            var sum = new SumAggregateParameters();
-            sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            sum.distinctAttributeParameters = distinctAttributeParameters;
-            aggParam = sum;
-        }
-        return aggParam;
-    }
-
-    public static GetAggregateParametersWithMargins(distinctAttributeParameters: AttributeParameters | undefined, atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
-        var aggregateParameters = new Array<AggregateParameters>();
-        atms.forEach(agg => {
-            var aggParams = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, agg);
-            if (aggParams) {
-                aggregateParameters.push(aggParams);
-
-                var margin = new MarginAggregateParameters();
-                margin.aggregateFunction = agg.AggregateFunction;
-                margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
-                margin.distinctAttributeParameters = distinctAttributeParameters;
-                aggregateParameters.push(margin);
-            }
-        });
-
-        return aggregateParameters;
-    }
-
-    public static GetBinningParameters(attr: AttributeTransformationModel, nrOfBins: number, minvalue?: number, maxvalue?: number): BinningParameters {
-        if (attr.AggregateFunction === AggregateFunction.None) {
-            return new EquiWidthBinningParameters(
-                {
-                    attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel),
-                    requestedNrOfBins: nrOfBins,
-                    minValue: minvalue,
-                    maxValue: maxvalue
-                });
-        }
-        else {
-            return new SingleBinBinningParameters(
-                {
-                    attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel)
-                });
-        }
-    }
-
-    public static GetAttributeParametersFromAttributeModel(am: AttributeModel): AttributeParameters {
-        if (am instanceof ColumnAttributeModel) {
-            return new AttributeColumnParameters(
-                {
-                    rawName: am.CodeName,
-                    visualizationHints: am.VisualizationHints
-                });
-        }
-        else if (am instanceof BackendAttributeModel) {
-            return new AttributeBackendParameters(
-                {
-                    rawName: am.CodeName,
-                    visualizationHints: am.VisualizationHints,
-                    id: (am).Id
-                });
-        }
-        else if (am instanceof CodeAttributeModel) {
-            return new AttributeCodeParameters(
-                {
-                    rawName: am.CodeName,
-                    visualizationHints: am.VisualizationHints,
-                    code: (am).Code
-                });
-        }
-        else {
-            throw new Exception();
-        }
-    }
-
-    public static GetAttributeParameters(am: AttributeModel): AttributeParameters {
-        return this.GetAttributeParametersFromAttributeModel(am);
-    }
-
-    public static OverlapBrushIndex(histogramResult: HistogramResult): number {
-        var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.Overlap);
-        return ModelHelpers.GetBrushIndex(histogramResult, brush);
-    }
-
-    public static AllBrushIndex(histogramResult: HistogramResult): number {
-        var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.All);
-        return ModelHelpers.GetBrushIndex(histogramResult, brush);
-    }
-
-    public static RestBrushIndex(histogramResult: HistogramResult): number {
-        var brush = ArrayUtil.First(histogramResult.brushes!, (b: Brush) => b.brushEnum === BrushEnum.Rest);
-        return ModelHelpers.GetBrushIndex(histogramResult, brush);
-    }
-
-    public static GetBrushIndex(histogramResult: HistogramResult, brush: Brush): number {
-        return ArrayUtil.IndexOfWithEqual(histogramResult.brushes!, brush);
-    }
-
-    public static GetAggregateResult(bin: Bin, aggregateKey: AggregateKey) {
-        if (aggregateKey.aggregateParameterIndex === -1 || aggregateKey.brushIndex === -1) {
-            return null;
-        }
-        return bin.aggregateResults![aggregateKey.aggregateParameterIndex! * bin.ySize! + aggregateKey.brushIndex!];
-    }
-
-    @action
-    public static PossibleAggegationFunctions(atm: AttributeTransformationModel): Array<AggregateFunction> {
-        var ret = new Array<AggregateFunction>();
-        ret.push(AggregateFunction.None);
-        ret.push(AggregateFunction.Count);
-        if (atm.AttributeModel.DataType === DataType.Float ||
-            atm.AttributeModel.DataType === DataType.Double ||
-            atm.AttributeModel.DataType === DataType.Int) {
-            ret.push(AggregateFunction.Avg);
-            ret.push(AggregateFunction.Sum);
-        }
-        return ret;
-    }
-
-    public static GetBinFilterModel(
-        bin: Bin, brushIndex: number, histogramResult: HistogramResult,
-        xAom: AttributeTransformationModel, yAom: AttributeTransformationModel): FilterModel {
-        var dimensions: Array<AttributeTransformationModel> = [xAom, yAom];
-        var filterModel = new FilterModel();
-
-        for (var i = 0; i < histogramResult.binRanges!.length; i++) {
-            if (!(histogramResult.binRanges![i] instanceof AggregateBinRange)) {
-                var binRange = VisualBinRangeHelper.GetNonAggregateVisualBinRange(histogramResult.binRanges![i]);
-                var dataFrom = binRange.GetValueFromIndex(bin.binIndex!.indices![i]);
-                var dataTo = binRange.AddStep(dataFrom);
-
-                if (binRange instanceof NominalVisualBinRange) {
-                    var tt = binRange.GetLabel(dataFrom);
-                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.EQUALS, tt));
-                }
-                else if (binRange instanceof AlphabeticVisualBinRange) {
-                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.STARTS_WITH,
-                        binRange.GetLabel(dataFrom)));
-                }
-                else {
-                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.GREATER_THAN_EQUAL, dataFrom));
-                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.LESS_THAN, dataTo));
-                }
-            }
-        }
-
-        return filterModel;
-    }
-
-    public GetAllAttributes(schema: Schema) {
-        if (!schema || !schema.rootAttributeGroup) {
-            return [];
-        }
-        const recurs = (attrs: Attribute[], g: AttributeGroup) => {
-            if (g.attributes) {
-                attrs.push.apply(attrs, g.attributes);
-                if (g.attributeGroups) {
-                    g.attributeGroups.forEach(ng => recurs(attrs, ng));
-                }
-            }
-        };
-        const allAttributes: Attribute[] = new Array<Attribute>();
-        recurs(allAttributes, schema.rootAttributeGroup);
-        return allAttributes;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
deleted file mode 100644
index 120b034f2..000000000
--- a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea';
-import { VisualBinRange } from './VisualBinRange';
-
-export class AlphabeticVisualBinRange extends VisualBinRange {
-    public DataBinRange: AlphabeticBinRange;
-
-    constructor(dataBinRange: AlphabeticBinRange) {
-        super();
-        this.DataBinRange = dataBinRange;
-    }
-
-    public AddStep(value: number): number {
-        return value + 1;
-    }
-
-    public GetValueFromIndex(index: number): number {
-        return index;
-    }
-
-    public GetBins(): number[] {
-        var bins = new Array<number>();
-        var idx = 0;
-        for (var key in this.DataBinRange.labelsValue) {
-            if (this.DataBinRange.labelsValue.hasOwnProperty(key)) {
-                bins.push(idx);
-                idx++;
-            }
-        }
-        return bins;
-    }
-
-    public GetLabel(value: number): string {
-        return this.DataBinRange.prefix + this.DataBinRange.valuesLabel![value];
-    }
-
-    public GetLabels(): Array<BinLabel> {
-        var labels = new Array<BinLabel>();
-        var count = 0;
-        for (var key in this.DataBinRange.valuesLabel) {
-            if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) {
-                var value = this.DataBinRange.valuesLabel[key];
-                labels.push(new BinLabel({
-                    value: parseFloat(key),
-                    minValue: count++,
-                    maxValue: count,
-                    label: this.DataBinRange.prefix + value
-                }));
-            }
-        }
-        return labels;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
deleted file mode 100644
index 776e643cd..000000000
--- a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea';
-import { VisualBinRange } from './VisualBinRange';
-
-export class DateTimeVisualBinRange extends VisualBinRange {
-    public DataBinRange: DateTimeBinRange;
-
-    constructor(dataBinRange: DateTimeBinRange) {
-        super();
-        this.DataBinRange = dataBinRange;
-    }
-
-    public AddStep(value: number): number {
-        return DateTimeVisualBinRange.AddToDateTimeTicks(value, this.DataBinRange.step!);
-    }
-
-    public GetValueFromIndex(index: number): number {
-        var v = this.DataBinRange.minValue!;
-        for (var i = 0; i < index; i++) {
-            v = this.AddStep(v);
-        }
-        return v;
-    }
-
-    public GetBins(): number[] {
-        var bins = new Array<number>();
-        for (var v: number = this.DataBinRange.minValue!;
-            v < this.DataBinRange.maxValue!;
-            v = DateTimeVisualBinRange.AddToDateTimeTicks(v, this.DataBinRange.step!)) {
-            bins.push(v);
-        }
-        return bins;
-    }
-
-    private pad(n: number, size: number) {
-        var sign = n < 0 ? '-' : '';
-        return sign + new Array(size).concat([Math.abs(n)]).join('0').slice(-size);
-    }
-
-
-    public GetLabel(value: number): string {
-        var dt = DateTimeVisualBinRange.TicksToDate(value);
-        if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Second ||
-            this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
-            return ("" + this.pad(dt.getMinutes(), 2) + ":" + this.pad(dt.getSeconds(), 2));
-            //return dt.ToString("mm:ss");
-        }
-        else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
-            return (this.pad(dt.getHours(), 2) + ":" + this.pad(dt.getMinutes(), 2));
-            //return dt.ToString("HH:mm");
-        }
-        else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
-            return ((dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear());
-            //return dt.ToString("MM/dd/yyyy");
-        }
-        else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
-            //return dt.ToString("MM/yyyy");
-            return ((dt.getMonth() + 1) + "/" + dt.getFullYear());
-        }
-        else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
-            return "" + dt.getFullYear();
-        }
-        return "n/a";
-    }
-
-    public static TicksToDate(ticks: number): Date {
-        var dd = new Date((ticks - 621355968000000000) / 10000);
-        dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset());
-        return dd;
-    }
-
-
-    public static DateToTicks(date: Date): number {
-        var copiedDate = new Date(date.getTime());
-        copiedDate.setMinutes(copiedDate.getMinutes() - copiedDate.getTimezoneOffset());
-        var t = copiedDate.getTime() * 10000 + 621355968000000000;
-        /*var dd = new Date((ticks - 621355968000000000) / 10000);
-        dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset());
-        return dd;*/
-        return t;
-    }
-
-    public static AddToDateTimeTicks(ticks: number, dateTimeStep: DateTimeStep): number {
-        var copiedDate = DateTimeVisualBinRange.TicksToDate(ticks);
-        var returnDate: Date = new Date(Date.now());
-        if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Second) {
-            returnDate = new Date(copiedDate.setSeconds(copiedDate.getSeconds() + dateTimeStep.dateTimeStepValue!));
-        }
-        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
-            returnDate = new Date(copiedDate.setMinutes(copiedDate.getMinutes() + dateTimeStep.dateTimeStepValue!));
-        }
-        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
-            returnDate = new Date(copiedDate.setHours(copiedDate.getHours() + dateTimeStep.dateTimeStepValue!));
-        }
-        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
-            returnDate = new Date(copiedDate.setDate(copiedDate.getDate() + dateTimeStep.dateTimeStepValue!));
-        }
-        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
-            returnDate = new Date(copiedDate.setMonth(copiedDate.getMonth() + dateTimeStep.dateTimeStepValue!));
-        }
-        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
-            returnDate = new Date(copiedDate.setFullYear(copiedDate.getFullYear() + dateTimeStep.dateTimeStepValue!));
-        }
-        return DateTimeVisualBinRange.DateToTicks(returnDate);
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
deleted file mode 100644
index 42509d797..000000000
--- a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { NominalBinRange, BinLabel } from '../../model/idea/idea';
-import { VisualBinRange } from './VisualBinRange';
-
-export class NominalVisualBinRange extends VisualBinRange {
-    public DataBinRange: NominalBinRange;
-
-    constructor(dataBinRange: NominalBinRange) {
-        super();
-        this.DataBinRange = dataBinRange;
-    }
-
-    public AddStep(value: number): number {
-        return value + 1;
-    }
-
-    public GetValueFromIndex(index: number): number {
-        return index;
-    }
-
-    public GetBins(): number[] {
-        var bins = new Array<number>();
-        var idx = 0;
-        for (var key in this.DataBinRange.labelsValue) {
-            if (this.DataBinRange.labelsValue.hasOwnProperty(key)) {
-                bins.push(idx);
-                idx++;
-            }
-        }
-        return bins;
-    }
-
-    public GetLabel(value: number): string {
-        return this.DataBinRange.valuesLabel![value];
-    }
-
-    public GetLabels(): Array<BinLabel> {
-        var labels = new Array<BinLabel>();
-        var count = 0;
-        for (var key in this.DataBinRange.valuesLabel) {
-            if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) {
-                var value = this.DataBinRange.valuesLabel[key];
-                labels.push(new BinLabel({
-                    value: parseFloat(key),
-                    minValue: count++,
-                    maxValue: count,
-                    label: value
-                }));
-            }
-        }
-        return labels;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
deleted file mode 100644
index 7bc097e1d..000000000
--- a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { QuantitativeBinRange } from '../idea/idea';
-import { VisualBinRange } from './VisualBinRange';
-import { format } from "d3-format";
-
-export class QuantitativeVisualBinRange extends VisualBinRange {
-
-    public DataBinRange: QuantitativeBinRange;
-
-    constructor(dataBinRange: QuantitativeBinRange) {
-        super();
-        this.DataBinRange = dataBinRange;
-    }
-
-    public AddStep(value: number): number {
-        return value + this.DataBinRange.step!;
-    }
-
-    public GetValueFromIndex(index: number): number {
-        return this.DataBinRange.minValue! + (index * this.DataBinRange.step!);
-    }
-
-    public GetLabel(value: number): string {
-        return QuantitativeVisualBinRange.NumberFormatter(value);
-    }
-
-    public static NumberFormatter(val: number): string {
-        if (val === 0) {
-            return "0";
-        }
-        if (val < 1) {
-            /*if (val < Math.abs(0.001))  {
-                return val.toExponential(2);
-            }*/
-            return format(".3")(val);
-        }
-        return format("~s")(val);
-    }
-
-    public GetBins(): number[] {
-        const bins = new Array<number>();
-
-        for (let v: number = this.DataBinRange.minValue!; v < this.DataBinRange.maxValue!; v += this.DataBinRange.step!) {
-            bins.push(v);
-        }
-        return bins;
-    }
-
-    public static Initialize(dataMinValue: number, dataMaxValue: number, targetBinNumber: number, isIntegerRange: boolean): QuantitativeVisualBinRange {
-        const extent = QuantitativeVisualBinRange.getExtent(dataMinValue, dataMaxValue, targetBinNumber, isIntegerRange);
-        const dataBinRange = new QuantitativeBinRange();
-        dataBinRange.minValue = extent[0];
-        dataBinRange.maxValue = extent[1];
-        dataBinRange.step = extent[2];
-
-        return new QuantitativeVisualBinRange(dataBinRange);
-    }
-
-    private static getExtent(dataMin: number, dataMax: number, m: number, isIntegerRange: boolean): number[] {
-        if (dataMin === dataMax) {
-            // dataMin -= 0.1;
-            dataMax += 0.1;
-        }
-        const span = dataMax - dataMin;
-
-        let step = Math.pow(10, Math.floor(Math.log10(span / m)));
-        const err = m / span * step;
-
-        if (err <= .15) {
-            step *= 10;
-        }
-        else if (err <= .35) {
-            step *= 5;
-        }
-        else if (err <= .75) {
-            step *= 2;
-        }
-
-        if (isIntegerRange) {
-            step = Math.ceil(step);
-        }
-        const ret: number[] = new Array<number>(3);
-        const minDivStep = Math.floor(dataMin / step);
-        const maxDivStep = Math.floor(dataMax / step);
-        ret[0] = minDivStep * step; // Math.floor(Math.Round(dataMin, 8)/step)*step;
-        ret[1] = maxDivStep * step + step; // Math.floor(Math.Round(dataMax, 8)/step)*step + step;
-        ret[2] = step;
-
-        return ret;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
deleted file mode 100644
index 449a22e91..000000000
--- a/src/client/northstar/model/binRanges/VisualBinRange.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { BinLabel } from '../../model/idea/idea';
-
-export abstract class VisualBinRange {
-
-    public abstract AddStep(value: number): number;
-
-    public abstract GetValueFromIndex(index: number): number;
-
-    public abstract GetBins(): Array<number>;
-
-    public GetLabel(value: number): string {
-        return value.toString();
-    }
-
-    public GetLabels(): Array<BinLabel> {
-        var labels = new Array<BinLabel>();
-        var bins = this.GetBins();
-        bins.forEach(b => {
-            labels.push(new BinLabel({
-                value: b,
-                minValue: b,
-                maxValue: this.AddStep(b),
-                label: this.GetLabel(b)
-            }));
-        });
-        return labels;
-    }
-}
-
-export enum ChartType {
-    HorizontalBar = 0, VerticalBar = 1, HeatMap = 2, SinglePoint = 3
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
deleted file mode 100644
index a92412686..000000000
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult, AttributeParameters } from "../idea/idea";
-import { VisualBinRange, ChartType } from "./VisualBinRange";
-import { NominalVisualBinRange } from "./NominalVisualBinRange";
-import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange";
-import { AlphabeticVisualBinRange } from "./AlphabeticVisualBinRange";
-import { DateTimeVisualBinRange } from "./DateTimeVisualBinRange";
-import { NorthstarSettings } from "../../manager/Gateway";
-import { ModelHelpers } from "../ModelHelpers";
-import { AttributeTransformationModel } from "../../core/attribute/AttributeTransformationModel";
-
-export const SETTINGS_X_BINS = 15;
-export const SETTINGS_Y_BINS = 15;
-export const SETTINGS_SAMPLE_SIZE = 100000;
-
-export class VisualBinRangeHelper {
-
-    public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange {
-        if (dataBinRange instanceof NominalBinRange) {
-            return new NominalVisualBinRange(dataBinRange);
-        }
-        else if (dataBinRange instanceof QuantitativeBinRange) {
-            return new QuantitativeVisualBinRange(dataBinRange);
-        }
-        else if (dataBinRange instanceof AlphabeticBinRange) {
-            return new AlphabeticVisualBinRange(dataBinRange);
-        }
-        else if (dataBinRange instanceof DateTimeBinRange) {
-            return new DateTimeVisualBinRange(dataBinRange);
-        }
-        throw new Exception();
-    }
-
-    public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
-
-        if (!(dataBinRange instanceof AggregateBinRange)) {
-            return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange);
-        }
-        else {
-            var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
-            var minValue = Number.MAX_VALUE;
-            var maxValue = Number.MIN_VALUE;
-            for (const brush of histoResult.brushes!) {
-                aggregateKey.brushIndex = brush.brushIndex;
-                for (var key in histoResult.bins) {
-                    if (histoResult.bins.hasOwnProperty(key)) {
-                        var bin = histoResult.bins[key];
-                        var res = <DoubleValueAggregateResult>ModelHelpers.GetAggregateResult(bin, aggregateKey);
-                        if (res && res.hasResult && res.result) {
-                            minValue = Math.min(minValue, res.result);
-                            maxValue = Math.max(maxValue, res.result);
-                        }
-                    }
-                }
-            }
-
-            let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false);
-
-            if (chartType === ChartType.HorizontalBar || chartType === ChartType.VerticalBar) {
-                visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue),
-                    Math.max(0, (visualBinRange).DataBinRange.maxValue!),
-                    SETTINGS_X_BINS, false);
-            }
-            else if (chartType === ChartType.SinglePoint) {
-                visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), Math.max(0, maxValue),
-                    SETTINGS_X_BINS, false);
-            }
-            return visualBinRange;
-        }
-    }
-}
diff --git a/src/client/northstar/model/idea/MetricTypeMapping.ts b/src/client/northstar/model/idea/MetricTypeMapping.ts
deleted file mode 100644
index e9759cf16..000000000
--- a/src/client/northstar/model/idea/MetricTypeMapping.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { MetricType } from "./Idea";
-import { Dictionary } from 'typescript-collections';
-
-
-export class MetricTypeMapping {
-
-    public static GetMetricInterpretation(metricType: MetricType): MetricInterpretation {
-        if (metricType === MetricType.Accuracy ||
-            metricType === MetricType.F1 ||
-            metricType === MetricType.F1Macro ||
-            metricType === MetricType.F1Micro ||
-            metricType === MetricType.JaccardSimilarityScore ||
-            metricType === MetricType.ObjectDetectionAveragePrecision ||
-            metricType === MetricType.Precision ||
-            metricType === MetricType.PrecisionAtTopK ||
-            metricType === MetricType.NormalizedMutualInformation ||
-            metricType === MetricType.Recall ||
-            metricType === MetricType.RocAucMacro ||
-            metricType === MetricType.RocAuc ||
-            metricType === MetricType.RocAucMicro ||
-            metricType === MetricType.RSquared) {
-            return MetricInterpretation.HigherIsBetter;
-        }
-        return MetricInterpretation.LowerIsBetter;
-    }
-}
-
-export enum MetricInterpretation {
-    HigherIsBetter, LowerIsBetter
-}
\ No newline at end of file
diff --git a/src/client/northstar/model/idea/idea.ts b/src/client/northstar/model/idea/idea.ts
deleted file mode 100644
index c73a822c7..000000000
--- a/src/client/northstar/model/idea/idea.ts
+++ /dev/null
@@ -1,8557 +0,0 @@
-/* tslint:disable */
-//----------------------
-// <auto-generated>
-//     Generated using the NSwag toolchain v11.19.2.0 (NJsonSchema v9.10.73.0 (Newtonsoft.Json v9.0.0.0)) (http://NSwag.org)
-// </auto-generated>
-//----------------------
-// ReSharper disable InconsistentNaming
-
-
-
-export enum AggregateFunction {
-    None = "None",
-    Sum = "Sum",
-    SumE = "SumE",
-    Count = "Count",
-    Min = "Min",
-    Max = "Max",
-    Avg = "Avg",
-}
-
-export abstract class AggregateParameters implements IAggregateParameters {
-
-    protected _discriminator: string;
-
-    public Equals(other: Object): boolean {
-        return this == other;
-    }
-    constructor(data?: IAggregateParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "AggregateParameters";
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): AggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "AverageAggregateParameters") {
-            let result = new AverageAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SingleDimensionAggregateParameters") {
-            throw new Error("The abstract class 'SingleDimensionAggregateParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "CountAggregateParameters") {
-            let result = new CountAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "KDEAggregateParameters") {
-            let result = new KDEAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "MarginAggregateParameters") {
-            let result = new MarginAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "MaxAggregateParameters") {
-            let result = new MaxAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "MinAggregateParameters") {
-            let result = new MinAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SumAggregateParameters") {
-            let result = new SumAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SumEstimationAggregateParameters") {
-            let result = new SumEstimationAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'AggregateParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        return data;
-    }
-}
-
-export interface IAggregateParameters {
-}
-
-export abstract class SingleDimensionAggregateParameters extends AggregateParameters implements ISingleDimensionAggregateParameters {
-    attributeParameters?: AttributeParameters | undefined;
-    distinctAttributeParameters?: AttributeParameters | undefined;
-
-    constructor(data?: ISingleDimensionAggregateParameters) {
-        super(data);
-        this._discriminator = "SingleDimensionAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.attributeParameters = data["AttributeParameters"] ? AttributeParameters.fromJS(data["AttributeParameters"]) : <any>undefined;
-            this.distinctAttributeParameters = data["DistinctAttributeParameters"] ? AttributeParameters.fromJS(data["DistinctAttributeParameters"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): SingleDimensionAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "AverageAggregateParameters") {
-            let result = new AverageAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CountAggregateParameters") {
-            let result = new CountAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "KDEAggregateParameters") {
-            let result = new KDEAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "MarginAggregateParameters") {
-            let result = new MarginAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "MaxAggregateParameters") {
-            let result = new MaxAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "MinAggregateParameters") {
-            let result = new MinAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SumAggregateParameters") {
-            let result = new SumAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SumEstimationAggregateParameters") {
-            let result = new SumEstimationAggregateParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'SingleDimensionAggregateParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["AttributeParameters"] = this.attributeParameters ? this.attributeParameters.toJSON() : <any>undefined;
-        data["DistinctAttributeParameters"] = this.distinctAttributeParameters ? this.distinctAttributeParameters.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISingleDimensionAggregateParameters extends IAggregateParameters {
-    attributeParameters?: AttributeParameters | undefined;
-    distinctAttributeParameters?: AttributeParameters | undefined;
-}
-
-export class AverageAggregateParameters extends SingleDimensionAggregateParameters implements IAverageAggregateParameters {
-
-    constructor(data?: IAverageAggregateParameters) {
-        super(data);
-        this._discriminator = "AverageAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): AverageAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new AverageAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAverageAggregateParameters extends ISingleDimensionAggregateParameters {
-}
-
-export abstract class AttributeParameters implements IAttributeParameters {
-    visualizationHints?: VisualizationHint[] | undefined;
-    rawName?: string | undefined;
-    public Equals(other: Object): boolean {
-        return this == other;
-    }
-
-    protected _discriminator: string;
-
-    constructor(data?: IAttributeParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "AttributeParameters";
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["VisualizationHints"] && data["VisualizationHints"].constructor === Array) {
-                this.visualizationHints = [];
-                for (let item of data["VisualizationHints"])
-                    this.visualizationHints.push(item);
-            }
-            this.rawName = data["RawName"];
-        }
-    }
-
-    static fromJS(data: any): AttributeParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "AttributeBackendParameters") {
-            let result = new AttributeBackendParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AttributeCaclculatedParameters") {
-            throw new Error("The abstract class 'AttributeCaclculatedParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "AttributeCodeParameters") {
-            let result = new AttributeCodeParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AttributeColumnParameters") {
-            let result = new AttributeColumnParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'AttributeParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        if (this.visualizationHints && this.visualizationHints.constructor === Array) {
-            data["VisualizationHints"] = [];
-            for (let item of this.visualizationHints)
-                data["VisualizationHints"].push(item);
-        }
-        data["RawName"] = this.rawName;
-        return data;
-    }
-}
-
-export interface IAttributeParameters {
-    visualizationHints?: VisualizationHint[] | undefined;
-    rawName?: string | undefined;
-}
-
-export enum VisualizationHint {
-    TreatAsEnumeration = "TreatAsEnumeration",
-    DefaultFlipAxis = "DefaultFlipAxis",
-    Image = "Image",
-}
-
-export abstract class AttributeCaclculatedParameters extends AttributeParameters implements IAttributeCaclculatedParameters {
-
-    constructor(data?: IAttributeCaclculatedParameters) {
-        super(data);
-        this._discriminator = "AttributeCaclculatedParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): AttributeCaclculatedParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "AttributeBackendParameters") {
-            let result = new AttributeBackendParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AttributeCodeParameters") {
-            let result = new AttributeCodeParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'AttributeCaclculatedParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAttributeCaclculatedParameters extends IAttributeParameters {
-}
-
-export class AttributeBackendParameters extends AttributeCaclculatedParameters implements IAttributeBackendParameters {
-    id?: string | undefined;
-
-    constructor(data?: IAttributeBackendParameters) {
-        super(data);
-        this._discriminator = "AttributeBackendParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.id = data["Id"];
-        }
-    }
-
-    static fromJS(data: any): AttributeBackendParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new AttributeBackendParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAttributeBackendParameters extends IAttributeCaclculatedParameters {
-    id?: string | undefined;
-}
-
-export class AttributeCodeParameters extends AttributeCaclculatedParameters implements IAttributeCodeParameters {
-    code?: string | undefined;
-
-    constructor(data?: IAttributeCodeParameters) {
-        super(data);
-        this._discriminator = "AttributeCodeParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.code = data["Code"];
-        }
-    }
-
-    static fromJS(data: any): AttributeCodeParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new AttributeCodeParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Code"] = this.code;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAttributeCodeParameters extends IAttributeCaclculatedParameters {
-    code?: string | undefined;
-}
-
-export class AttributeColumnParameters extends AttributeParameters implements IAttributeColumnParameters {
-
-    constructor(data?: IAttributeColumnParameters) {
-        super(data);
-        this._discriminator = "AttributeColumnParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): AttributeColumnParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new AttributeColumnParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAttributeColumnParameters extends IAttributeParameters {
-}
-
-export class CountAggregateParameters extends SingleDimensionAggregateParameters implements ICountAggregateParameters {
-
-    constructor(data?: ICountAggregateParameters) {
-        super(data);
-        this._discriminator = "CountAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): CountAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new CountAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ICountAggregateParameters extends ISingleDimensionAggregateParameters {
-}
-
-export class KDEAggregateParameters extends SingleDimensionAggregateParameters implements IKDEAggregateParameters {
-    nrOfSamples?: number | undefined;
-
-    constructor(data?: IKDEAggregateParameters) {
-        super(data);
-        this._discriminator = "KDEAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.nrOfSamples = data["NrOfSamples"];
-        }
-    }
-
-    static fromJS(data: any): KDEAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new KDEAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["NrOfSamples"] = this.nrOfSamples;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IKDEAggregateParameters extends ISingleDimensionAggregateParameters {
-    nrOfSamples?: number | undefined;
-}
-
-export class MarginAggregateParameters extends SingleDimensionAggregateParameters implements IMarginAggregateParameters {
-    aggregateFunction?: AggregateFunction | undefined;
-
-    constructor(data?: IMarginAggregateParameters) {
-        super(data);
-        this._discriminator = "MarginAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.aggregateFunction = data["AggregateFunction"];
-        }
-    }
-
-    static fromJS(data: any): MarginAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new MarginAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["AggregateFunction"] = this.aggregateFunction;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IMarginAggregateParameters extends ISingleDimensionAggregateParameters {
-    aggregateFunction?: AggregateFunction | undefined;
-}
-
-export class MaxAggregateParameters extends SingleDimensionAggregateParameters implements IMaxAggregateParameters {
-
-    constructor(data?: IMaxAggregateParameters) {
-        super(data);
-        this._discriminator = "MaxAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): MaxAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new MaxAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IMaxAggregateParameters extends ISingleDimensionAggregateParameters {
-}
-
-export class MinAggregateParameters extends SingleDimensionAggregateParameters implements IMinAggregateParameters {
-
-    constructor(data?: IMinAggregateParameters) {
-        super(data);
-        this._discriminator = "MinAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): MinAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new MinAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IMinAggregateParameters extends ISingleDimensionAggregateParameters {
-}
-
-export class SumAggregateParameters extends SingleDimensionAggregateParameters implements ISumAggregateParameters {
-
-    constructor(data?: ISumAggregateParameters) {
-        super(data);
-        this._discriminator = "SumAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): SumAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new SumAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISumAggregateParameters extends ISingleDimensionAggregateParameters {
-}
-
-export class SumEstimationAggregateParameters extends SingleDimensionAggregateParameters implements ISumEstimationAggregateParameters {
-
-    constructor(data?: ISumEstimationAggregateParameters) {
-        super(data);
-        this._discriminator = "SumEstimationAggregateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): SumEstimationAggregateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new SumEstimationAggregateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISumEstimationAggregateParameters extends ISingleDimensionAggregateParameters {
-}
-
-export enum OrderingFunction {
-    None = 0,
-    SortUp = 1,
-    SortDown = 2,
-}
-
-export abstract class BinningParameters implements IBinningParameters {
-    attributeParameters?: AttributeParameters | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IBinningParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "BinningParameters";
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.attributeParameters = data["AttributeParameters"] ? AttributeParameters.fromJS(data["AttributeParameters"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): BinningParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "EquiWidthBinningParameters") {
-            let result = new EquiWidthBinningParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SingleBinBinningParameters") {
-            let result = new SingleBinBinningParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'BinningParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["AttributeParameters"] = this.attributeParameters ? this.attributeParameters.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface IBinningParameters {
-    attributeParameters?: AttributeParameters | undefined;
-}
-
-export class EquiWidthBinningParameters extends BinningParameters implements IEquiWidthBinningParameters {
-    minValue?: number | undefined;
-    maxValue?: number | undefined;
-    requestedNrOfBins?: number | undefined;
-    referenceValue?: number | undefined;
-    step?: number | undefined;
-
-    constructor(data?: IEquiWidthBinningParameters) {
-        super(data);
-        this._discriminator = "EquiWidthBinningParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.minValue = data["MinValue"];
-            this.maxValue = data["MaxValue"];
-            this.requestedNrOfBins = data["RequestedNrOfBins"];
-            this.referenceValue = data["ReferenceValue"];
-            this.step = data["Step"];
-        }
-    }
-
-    static fromJS(data: any): EquiWidthBinningParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new EquiWidthBinningParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["MinValue"] = this.minValue;
-        data["MaxValue"] = this.maxValue;
-        data["RequestedNrOfBins"] = this.requestedNrOfBins;
-        data["ReferenceValue"] = this.referenceValue;
-        data["Step"] = this.step;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IEquiWidthBinningParameters extends IBinningParameters {
-    minValue?: number | undefined;
-    maxValue?: number | undefined;
-    requestedNrOfBins?: number | undefined;
-    referenceValue?: number | undefined;
-    step?: number | undefined;
-}
-
-export class SingleBinBinningParameters extends BinningParameters implements ISingleBinBinningParameters {
-
-    constructor(data?: ISingleBinBinningParameters) {
-        super(data);
-        this._discriminator = "SingleBinBinningParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): SingleBinBinningParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new SingleBinBinningParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISingleBinBinningParameters extends IBinningParameters {
-}
-
-export class Attribute implements IAttribute {
-    displayName?: string | undefined;
-    rawName?: string | undefined;
-    description?: string | undefined;
-    dataType?: DataType | undefined;
-    visualizationHints?: VisualizationHint[] | undefined;
-    isTarget?: boolean | undefined;
-
-    constructor(data?: IAttribute) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.displayName = data["DisplayName"];
-            this.rawName = data["RawName"];
-            this.description = data["Description"];
-            this.dataType = data["DataType"];
-            if (data["VisualizationHints"] && data["VisualizationHints"].constructor === Array) {
-                this.visualizationHints = [];
-                for (let item of data["VisualizationHints"])
-                    this.visualizationHints.push(item);
-            }
-            this.isTarget = data["IsTarget"];
-        }
-    }
-
-    static fromJS(data: any): Attribute {
-        data = typeof data === 'object' ? data : {};
-        let result = new Attribute();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["DisplayName"] = this.displayName;
-        data["RawName"] = this.rawName;
-        data["Description"] = this.description;
-        data["DataType"] = this.dataType;
-        if (this.visualizationHints && this.visualizationHints.constructor === Array) {
-            data["VisualizationHints"] = [];
-            for (let item of this.visualizationHints)
-                data["VisualizationHints"].push(item);
-        }
-        data["IsTarget"] = this.isTarget;
-        return data;
-    }
-}
-
-export interface IAttribute {
-    displayName?: string | undefined;
-    rawName?: string | undefined;
-    description?: string | undefined;
-    dataType?: DataType | undefined;
-    visualizationHints?: VisualizationHint[] | undefined;
-    isTarget?: boolean | undefined;
-}
-
-export enum DataType {
-    Int = "Int",
-    String = "String",
-    Float = "Float",
-    Double = "Double",
-    DateTime = "DateTime",
-    Object = "Object",
-    Undefined = "Undefined",
-}
-
-export class AttributeGroup implements IAttributeGroup {
-    name?: string | undefined;
-    attributeGroups?: AttributeGroup[] | undefined;
-    attributes?: Attribute[] | undefined;
-
-    constructor(data?: IAttributeGroup) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.name = data["Name"];
-            if (data["AttributeGroups"] && data["AttributeGroups"].constructor === Array) {
-                this.attributeGroups = [];
-                for (let item of data["AttributeGroups"])
-                    this.attributeGroups.push(AttributeGroup.fromJS(item));
-            }
-            if (data["Attributes"] && data["Attributes"].constructor === Array) {
-                this.attributes = [];
-                for (let item of data["Attributes"])
-                    this.attributes.push(Attribute.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): AttributeGroup {
-        data = typeof data === 'object' ? data : {};
-        let result = new AttributeGroup();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Name"] = this.name;
-        if (this.attributeGroups && this.attributeGroups.constructor === Array) {
-            data["AttributeGroups"] = [];
-            for (let item of this.attributeGroups)
-                data["AttributeGroups"].push(item.toJSON());
-        }
-        if (this.attributes && this.attributes.constructor === Array) {
-            data["Attributes"] = [];
-            for (let item of this.attributes)
-                data["Attributes"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IAttributeGroup {
-    name?: string | undefined;
-    attributeGroups?: AttributeGroup[] | undefined;
-    attributes?: Attribute[] | undefined;
-}
-
-export class Catalog implements ICatalog {
-    supportedOperations?: string[] | undefined;
-    schemas?: Schema[] | undefined;
-
-    constructor(data?: ICatalog) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["SupportedOperations"] && data["SupportedOperations"].constructor === Array) {
-                this.supportedOperations = [];
-                for (let item of data["SupportedOperations"])
-                    this.supportedOperations.push(item);
-            }
-            if (data["Schemas"] && data["Schemas"].constructor === Array) {
-                this.schemas = [];
-                for (let item of data["Schemas"])
-                    this.schemas.push(Schema.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): Catalog {
-        data = typeof data === 'object' ? data : {};
-        let result = new Catalog();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.supportedOperations && this.supportedOperations.constructor === Array) {
-            data["SupportedOperations"] = [];
-            for (let item of this.supportedOperations)
-                data["SupportedOperations"].push(item);
-        }
-        if (this.schemas && this.schemas.constructor === Array) {
-            data["Schemas"] = [];
-            for (let item of this.schemas)
-                data["Schemas"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface ICatalog {
-    supportedOperations?: string[] | undefined;
-    schemas?: Schema[] | undefined;
-}
-
-export class Schema implements ISchema {
-    rootAttributeGroup?: AttributeGroup | undefined;
-    displayName?: string | undefined;
-    augmentedFrom?: string | undefined;
-    rawName?: string | undefined;
-    problemDescription?: string | undefined;
-    darpaProblemDoc?: DarpaProblemDoc | undefined;
-    distinctAttributeParameters?: AttributeParameters | undefined;
-    darpaDatasetDoc?: DarpaDatasetDoc | undefined;
-    darpaDatasetLocation?: string | undefined;
-    isMultiResourceData?: boolean | undefined;
-    problemFinderRows?: ProblemFinderRows[] | undefined;
-    correlationRows?: ProblemFinderRows[] | undefined;
-
-    constructor(data?: ISchema) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.rootAttributeGroup = data["RootAttributeGroup"] ? AttributeGroup.fromJS(data["RootAttributeGroup"]) : <any>undefined;
-            this.displayName = data["DisplayName"];
-            this.augmentedFrom = data["AugmentedFrom"];
-            this.rawName = data["RawName"];
-            this.problemDescription = data["ProblemDescription"];
-            this.darpaProblemDoc = data["DarpaProblemDoc"] ? DarpaProblemDoc.fromJS(data["DarpaProblemDoc"]) : <any>undefined;
-            this.distinctAttributeParameters = data["DistinctAttributeParameters"] ? AttributeParameters.fromJS(data["DistinctAttributeParameters"]) : <any>undefined;
-            this.darpaDatasetDoc = data["DarpaDatasetDoc"] ? DarpaDatasetDoc.fromJS(data["DarpaDatasetDoc"]) : <any>undefined;
-            this.darpaDatasetLocation = data["DarpaDatasetLocation"];
-            this.isMultiResourceData = data["IsMultiResourceData"];
-            if (data["ProblemFinderRows"] && data["ProblemFinderRows"].constructor === Array) {
-                this.problemFinderRows = [];
-                for (let item of data["ProblemFinderRows"])
-                    this.problemFinderRows.push(ProblemFinderRows.fromJS(item));
-            }
-            if (data["CorrelationRows"] && data["CorrelationRows"].constructor === Array) {
-                this.correlationRows = [];
-                for (let item of data["CorrelationRows"])
-                    this.correlationRows.push(ProblemFinderRows.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): Schema {
-        data = typeof data === 'object' ? data : {};
-        let result = new Schema();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["RootAttributeGroup"] = this.rootAttributeGroup ? this.rootAttributeGroup.toJSON() : <any>undefined;
-        data["DisplayName"] = this.displayName;
-        data["AugmentedFrom"] = this.augmentedFrom;
-        data["RawName"] = this.rawName;
-        data["ProblemDescription"] = this.problemDescription;
-        data["DarpaProblemDoc"] = this.darpaProblemDoc ? this.darpaProblemDoc.toJSON() : <any>undefined;
-        data["DistinctAttributeParameters"] = this.distinctAttributeParameters ? this.distinctAttributeParameters.toJSON() : <any>undefined;
-        data["DarpaDatasetDoc"] = this.darpaDatasetDoc ? this.darpaDatasetDoc.toJSON() : <any>undefined;
-        data["DarpaDatasetLocation"] = this.darpaDatasetLocation;
-        data["IsMultiResourceData"] = this.isMultiResourceData;
-        if (this.problemFinderRows && this.problemFinderRows.constructor === Array) {
-            data["ProblemFinderRows"] = [];
-            for (let item of this.problemFinderRows)
-                data["ProblemFinderRows"].push(item.toJSON());
-        }
-        if (this.correlationRows && this.correlationRows.constructor === Array) {
-            data["CorrelationRows"] = [];
-            for (let item of this.correlationRows)
-                data["CorrelationRows"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface ISchema {
-    rootAttributeGroup?: AttributeGroup | undefined;
-    displayName?: string | undefined;
-    augmentedFrom?: string | undefined;
-    rawName?: string | undefined;
-    problemDescription?: string | undefined;
-    darpaProblemDoc?: DarpaProblemDoc | undefined;
-    distinctAttributeParameters?: AttributeParameters | undefined;
-    darpaDatasetDoc?: DarpaDatasetDoc | undefined;
-    darpaDatasetLocation?: string | undefined;
-    isMultiResourceData?: boolean | undefined;
-    problemFinderRows?: ProblemFinderRows[] | undefined;
-    correlationRows?: ProblemFinderRows[] | undefined;
-}
-
-export class DarpaProblemDoc implements IDarpaProblemDoc {
-    about?: ProblemAbout | undefined;
-    inputs?: ProblemInputs | undefined;
-
-    constructor(data?: IDarpaProblemDoc) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.about = data["about"] ? ProblemAbout.fromJS(data["about"]) : <any>undefined;
-            this.inputs = data["inputs"] ? ProblemInputs.fromJS(data["inputs"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): DarpaProblemDoc {
-        data = typeof data === 'object' ? data : {};
-        let result = new DarpaProblemDoc();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["about"] = this.about ? this.about.toJSON() : <any>undefined;
-        data["inputs"] = this.inputs ? this.inputs.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface IDarpaProblemDoc {
-    about?: ProblemAbout | undefined;
-    inputs?: ProblemInputs | undefined;
-}
-
-export class ProblemAbout implements IProblemAbout {
-    problemID?: string | undefined;
-    problemName?: string | undefined;
-    problemDescription?: string | undefined;
-    taskType?: string | undefined;
-    taskSubType?: string | undefined;
-    problemSchemaVersion?: string | undefined;
-    problemVersion?: string | undefined;
-
-    constructor(data?: IProblemAbout) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.problemID = data["problemID"];
-            this.problemName = data["problemName"];
-            this.problemDescription = data["problemDescription"];
-            this.taskType = data["taskType"];
-            this.taskSubType = data["taskSubType"];
-            this.problemSchemaVersion = data["problemSchemaVersion"];
-            this.problemVersion = data["problemVersion"];
-        }
-    }
-
-    static fromJS(data: any): ProblemAbout {
-        data = typeof data === 'object' ? data : {};
-        let result = new ProblemAbout();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["problemID"] = this.problemID;
-        data["problemName"] = this.problemName;
-        data["problemDescription"] = this.problemDescription;
-        data["taskType"] = this.taskType;
-        data["taskSubType"] = this.taskSubType;
-        data["problemSchemaVersion"] = this.problemSchemaVersion;
-        data["problemVersion"] = this.problemVersion;
-        return data;
-    }
-}
-
-export interface IProblemAbout {
-    problemID?: string | undefined;
-    problemName?: string | undefined;
-    problemDescription?: string | undefined;
-    taskType?: string | undefined;
-    taskSubType?: string | undefined;
-    problemSchemaVersion?: string | undefined;
-    problemVersion?: string | undefined;
-}
-
-export class ProblemInputs implements IProblemInputs {
-    data?: ProblemData[] | undefined;
-    performanceMetrics?: ProblemPerformanceMetric[] | undefined;
-
-    constructor(data?: IProblemInputs) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["data"] && data["data"].constructor === Array) {
-                this.data = [];
-                for (let item of data["data"])
-                    this.data.push(ProblemData.fromJS(item));
-            }
-            if (data["performanceMetrics"] && data["performanceMetrics"].constructor === Array) {
-                this.performanceMetrics = [];
-                for (let item of data["performanceMetrics"])
-                    this.performanceMetrics.push(ProblemPerformanceMetric.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): ProblemInputs {
-        data = typeof data === 'object' ? data : {};
-        let result = new ProblemInputs();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.data && this.data.constructor === Array) {
-            data["data"] = [];
-            for (let item of this.data)
-                data["data"].push(item.toJSON());
-        }
-        if (this.performanceMetrics && this.performanceMetrics.constructor === Array) {
-            data["performanceMetrics"] = [];
-            for (let item of this.performanceMetrics)
-                data["performanceMetrics"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IProblemInputs {
-    data?: ProblemData[] | undefined;
-    performanceMetrics?: ProblemPerformanceMetric[] | undefined;
-}
-
-export class ProblemData implements IProblemData {
-    datasetID?: string | undefined;
-    targets?: ProblemTarget[] | undefined;
-
-    constructor(data?: IProblemData) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.datasetID = data["datasetID"];
-            if (data["targets"] && data["targets"].constructor === Array) {
-                this.targets = [];
-                for (let item of data["targets"])
-                    this.targets.push(ProblemTarget.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): ProblemData {
-        data = typeof data === 'object' ? data : {};
-        let result = new ProblemData();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["datasetID"] = this.datasetID;
-        if (this.targets && this.targets.constructor === Array) {
-            data["targets"] = [];
-            for (let item of this.targets)
-                data["targets"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IProblemData {
-    datasetID?: string | undefined;
-    targets?: ProblemTarget[] | undefined;
-}
-
-export class ProblemTarget implements IProblemTarget {
-    targetIndex?: number | undefined;
-    resID?: string | undefined;
-    colIndex?: number | undefined;
-    colName?: string | undefined;
-
-    constructor(data?: IProblemTarget) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.targetIndex = data["targetIndex"];
-            this.resID = data["resID"];
-            this.colIndex = data["colIndex"];
-            this.colName = data["colName"];
-        }
-    }
-
-    static fromJS(data: any): ProblemTarget {
-        data = typeof data === 'object' ? data : {};
-        let result = new ProblemTarget();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["targetIndex"] = this.targetIndex;
-        data["resID"] = this.resID;
-        data["colIndex"] = this.colIndex;
-        data["colName"] = this.colName;
-        return data;
-    }
-}
-
-export interface IProblemTarget {
-    targetIndex?: number | undefined;
-    resID?: string | undefined;
-    colIndex?: number | undefined;
-    colName?: string | undefined;
-}
-
-export class ProblemPerformanceMetric implements IProblemPerformanceMetric {
-    metric?: string | undefined;
-
-    constructor(data?: IProblemPerformanceMetric) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.metric = data["metric"];
-        }
-    }
-
-    static fromJS(data: any): ProblemPerformanceMetric {
-        data = typeof data === 'object' ? data : {};
-        let result = new ProblemPerformanceMetric();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["metric"] = this.metric;
-        return data;
-    }
-}
-
-export interface IProblemPerformanceMetric {
-    metric?: string | undefined;
-}
-
-export class DarpaDatasetDoc implements IDarpaDatasetDoc {
-    about?: DatasetAbout | undefined;
-    dataResources?: Resource[] | undefined;
-
-    constructor(data?: IDarpaDatasetDoc) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.about = data["about"] ? DatasetAbout.fromJS(data["about"]) : <any>undefined;
-            if (data["dataResources"] && data["dataResources"].constructor === Array) {
-                this.dataResources = [];
-                for (let item of data["dataResources"])
-                    this.dataResources.push(Resource.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): DarpaDatasetDoc {
-        data = typeof data === 'object' ? data : {};
-        let result = new DarpaDatasetDoc();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["about"] = this.about ? this.about.toJSON() : <any>undefined;
-        if (this.dataResources && this.dataResources.constructor === Array) {
-            data["dataResources"] = [];
-            for (let item of this.dataResources)
-                data["dataResources"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IDarpaDatasetDoc {
-    about?: DatasetAbout | undefined;
-    dataResources?: Resource[] | undefined;
-}
-
-export class DatasetAbout implements IDatasetAbout {
-    datasetID?: string | undefined;
-    datasetName?: string | undefined;
-    description?: string | undefined;
-    citation?: string | undefined;
-    license?: string | undefined;
-    source?: string | undefined;
-    sourceURI?: string | undefined;
-    approximateSize?: string | undefined;
-    datasetSchemaVersion?: string | undefined;
-    redacted?: boolean | undefined;
-    datasetVersion?: string | undefined;
-
-    constructor(data?: IDatasetAbout) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.datasetID = data["datasetID"];
-            this.datasetName = data["datasetName"];
-            this.description = data["description"];
-            this.citation = data["citation"];
-            this.license = data["license"];
-            this.source = data["source"];
-            this.sourceURI = data["sourceURI"];
-            this.approximateSize = data["approximateSize"];
-            this.datasetSchemaVersion = data["datasetSchemaVersion"];
-            this.redacted = data["redacted"];
-            this.datasetVersion = data["datasetVersion"];
-        }
-    }
-
-    static fromJS(data: any): DatasetAbout {
-        data = typeof data === 'object' ? data : {};
-        let result = new DatasetAbout();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["datasetID"] = this.datasetID;
-        data["datasetName"] = this.datasetName;
-        data["description"] = this.description;
-        data["citation"] = this.citation;
-        data["license"] = this.license;
-        data["source"] = this.source;
-        data["sourceURI"] = this.sourceURI;
-        data["approximateSize"] = this.approximateSize;
-        data["datasetSchemaVersion"] = this.datasetSchemaVersion;
-        data["redacted"] = this.redacted;
-        data["datasetVersion"] = this.datasetVersion;
-        return data;
-    }
-}
-
-export interface IDatasetAbout {
-    datasetID?: string | undefined;
-    datasetName?: string | undefined;
-    description?: string | undefined;
-    citation?: string | undefined;
-    license?: string | undefined;
-    source?: string | undefined;
-    sourceURI?: string | undefined;
-    approximateSize?: string | undefined;
-    datasetSchemaVersion?: string | undefined;
-    redacted?: boolean | undefined;
-    datasetVersion?: string | undefined;
-}
-
-export class Resource implements IResource {
-    resID?: string | undefined;
-    resPath?: string | undefined;
-    resType?: string | undefined;
-    resFormat?: string[] | undefined;
-    columns?: Column[] | undefined;
-    isCollection?: boolean | undefined;
-
-    constructor(data?: IResource) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.resID = data["resID"];
-            this.resPath = data["resPath"];
-            this.resType = data["resType"];
-            if (data["resFormat"] && data["resFormat"].constructor === Array) {
-                this.resFormat = [];
-                for (let item of data["resFormat"])
-                    this.resFormat.push(item);
-            }
-            if (data["columns"] && data["columns"].constructor === Array) {
-                this.columns = [];
-                for (let item of data["columns"])
-                    this.columns.push(Column.fromJS(item));
-            }
-            this.isCollection = data["isCollection"];
-        }
-    }
-
-    static fromJS(data: any): Resource {
-        data = typeof data === 'object' ? data : {};
-        let result = new Resource();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["resID"] = this.resID;
-        data["resPath"] = this.resPath;
-        data["resType"] = this.resType;
-        if (this.resFormat && this.resFormat.constructor === Array) {
-            data["resFormat"] = [];
-            for (let item of this.resFormat)
-                data["resFormat"].push(item);
-        }
-        if (this.columns && this.columns.constructor === Array) {
-            data["columns"] = [];
-            for (let item of this.columns)
-                data["columns"].push(item.toJSON());
-        }
-        data["isCollection"] = this.isCollection;
-        return data;
-    }
-}
-
-export interface IResource {
-    resID?: string | undefined;
-    resPath?: string | undefined;
-    resType?: string | undefined;
-    resFormat?: string[] | undefined;
-    columns?: Column[] | undefined;
-    isCollection?: boolean | undefined;
-}
-
-export class Column implements IColumn {
-    colIndex?: number | undefined;
-    colDescription?: string | undefined;
-    colName?: string | undefined;
-    colType?: string | undefined;
-    role?: string[] | undefined;
-    refersTo?: Reference | undefined;
-
-    constructor(data?: IColumn) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.colIndex = data["colIndex"];
-            this.colDescription = data["colDescription"];
-            this.colName = data["colName"];
-            this.colType = data["colType"];
-            if (data["role"] && data["role"].constructor === Array) {
-                this.role = [];
-                for (let item of data["role"])
-                    this.role.push(item);
-            }
-            this.refersTo = data["refersTo"] ? Reference.fromJS(data["refersTo"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): Column {
-        data = typeof data === 'object' ? data : {};
-        let result = new Column();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["colIndex"] = this.colIndex;
-        data["colDescription"] = this.colDescription;
-        data["colName"] = this.colName;
-        data["colType"] = this.colType;
-        if (this.role && this.role.constructor === Array) {
-            data["role"] = [];
-            for (let item of this.role)
-                data["role"].push(item);
-        }
-        data["refersTo"] = this.refersTo ? this.refersTo.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface IColumn {
-    colIndex?: number | undefined;
-    colDescription?: string | undefined;
-    colName?: string | undefined;
-    colType?: string | undefined;
-    role?: string[] | undefined;
-    refersTo?: Reference | undefined;
-}
-
-export class Reference implements IReference {
-    resID?: string | undefined;
-
-    constructor(data?: IReference) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.resID = data["resID"];
-        }
-    }
-
-    static fromJS(data: any): Reference {
-        data = typeof data === 'object' ? data : {};
-        let result = new Reference();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["resID"] = this.resID;
-        return data;
-    }
-}
-
-export interface IReference {
-    resID?: string | undefined;
-}
-
-export class ProblemFinderRows implements IProblemFinderRows {
-    label?: string | undefined;
-    type?: string | undefined;
-    features?: Feature[] | undefined;
-
-    constructor(data?: IProblemFinderRows) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.label = data["label"];
-            this.type = data["type"];
-            if (data["features"] && data["features"].constructor === Array) {
-                this.features = [];
-                for (let item of data["features"])
-                    this.features.push(Feature.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): ProblemFinderRows {
-        data = typeof data === 'object' ? data : {};
-        let result = new ProblemFinderRows();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["label"] = this.label;
-        data["type"] = this.type;
-        if (this.features && this.features.constructor === Array) {
-            data["features"] = [];
-            for (let item of this.features)
-                data["features"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IProblemFinderRows {
-    label?: string | undefined;
-    type?: string | undefined;
-    features?: Feature[] | undefined;
-}
-
-export class Feature implements IFeature {
-    name?: string | undefined;
-    selected?: boolean | undefined;
-    value?: number | undefined;
-
-    constructor(data?: IFeature) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.name = data["name"];
-            this.selected = data["selected"];
-            this.value = data["value"];
-        }
-    }
-
-    static fromJS(data: any): Feature {
-        data = typeof data === 'object' ? data : {};
-        let result = new Feature();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["name"] = this.name;
-        data["selected"] = this.selected;
-        data["value"] = this.value;
-        return data;
-    }
-}
-
-export interface IFeature {
-    name?: string | undefined;
-    selected?: boolean | undefined;
-    value?: number | undefined;
-}
-
-export enum DataType2 {
-    Int = 0,
-    String = 1,
-    Float = 2,
-    Double = 3,
-    DateTime = 4,
-    Object = 5,
-    Undefined = 6,
-}
-
-export abstract class DataTypeExtensions implements IDataTypeExtensions {
-
-    constructor(data?: IDataTypeExtensions) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): DataTypeExtensions {
-        data = typeof data === 'object' ? data : {};
-        throw new Error("The abstract class 'DataTypeExtensions' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        return data;
-    }
-}
-
-export interface IDataTypeExtensions {
-}
-
-export class ResObject implements IResObject {
-    columnName?: string | undefined;
-
-    constructor(data?: IResObject) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.columnName = data["columnName"];
-        }
-    }
-
-    static fromJS(data: any): ResObject {
-        data = typeof data === 'object' ? data : {};
-        let result = new ResObject();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["columnName"] = this.columnName;
-        return data;
-    }
-}
-
-export interface IResObject {
-    columnName?: string | undefined;
-}
-
-export class Exception implements IException {
-    message?: string | undefined;
-    innerException?: Exception | undefined;
-    stackTrace?: string | undefined;
-    source?: string | undefined;
-
-    constructor(data?: IException) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.message = data["Message"];
-            this.innerException = data["InnerException"] ? Exception.fromJS(data["InnerException"]) : <any>undefined;
-            this.stackTrace = data["StackTrace"];
-            this.source = data["Source"];
-        }
-    }
-
-    static fromJS(data: any): Exception {
-        data = typeof data === 'object' ? data : {};
-        let result = new Exception();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Message"] = this.message;
-        data["InnerException"] = this.innerException ? this.innerException.toJSON() : <any>undefined;
-        data["StackTrace"] = this.stackTrace;
-        data["Source"] = this.source;
-        return data;
-    }
-}
-
-export interface IException {
-    message?: string | undefined;
-    innerException?: Exception | undefined;
-    stackTrace?: string | undefined;
-    source?: string | undefined;
-}
-
-export class IDEAException extends Exception implements IIDEAException {
-
-    constructor(data?: IIDEAException) {
-        super(data);
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): IDEAException {
-        data = typeof data === 'object' ? data : {};
-        let result = new IDEAException();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IIDEAException extends IException {
-}
-
-export class CodeParameters implements ICodeParameters {
-    attributeCodeParameters?: AttributeCodeParameters[] | undefined;
-    adapterName?: string | undefined;
-
-    constructor(data?: ICodeParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) {
-                this.attributeCodeParameters = [];
-                for (let item of data["AttributeCodeParameters"])
-                    this.attributeCodeParameters.push(AttributeCodeParameters.fromJS(item));
-            }
-            this.adapterName = data["AdapterName"];
-        }
-    }
-
-    static fromJS(data: any): CodeParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new CodeParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) {
-            data["AttributeCodeParameters"] = [];
-            for (let item of this.attributeCodeParameters)
-                data["AttributeCodeParameters"].push(item.toJSON());
-        }
-        data["AdapterName"] = this.adapterName;
-        return data;
-    }
-}
-
-export interface ICodeParameters {
-    attributeCodeParameters?: AttributeCodeParameters[] | undefined;
-    adapterName?: string | undefined;
-}
-
-export class CompileResult implements ICompileResult {
-    compileSuccess?: boolean | undefined;
-    compileMessage?: string | undefined;
-    dataType?: DataType | undefined;
-    replaceAttributeParameters?: AttributeParameters[] | undefined;
-
-    constructor(data?: ICompileResult) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.compileSuccess = data["CompileSuccess"];
-            this.compileMessage = data["CompileMessage"];
-            this.dataType = data["DataType"];
-            if (data["ReplaceAttributeParameters"] && data["ReplaceAttributeParameters"].constructor === Array) {
-                this.replaceAttributeParameters = [];
-                for (let item of data["ReplaceAttributeParameters"])
-                    this.replaceAttributeParameters.push(AttributeParameters.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): CompileResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new CompileResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["CompileSuccess"] = this.compileSuccess;
-        data["CompileMessage"] = this.compileMessage;
-        data["DataType"] = this.dataType;
-        if (this.replaceAttributeParameters && this.replaceAttributeParameters.constructor === Array) {
-            data["ReplaceAttributeParameters"] = [];
-            for (let item of this.replaceAttributeParameters)
-                data["ReplaceAttributeParameters"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface ICompileResult {
-    compileSuccess?: boolean | undefined;
-    compileMessage?: string | undefined;
-    dataType?: DataType | undefined;
-    replaceAttributeParameters?: AttributeParameters[] | undefined;
-}
-
-export class CompileResults implements ICompileResults {
-    rawNameToCompileResult?: { [key: string]: CompileResult; } | undefined;
-
-    constructor(data?: ICompileResults) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["RawNameToCompileResult"]) {
-                this.rawNameToCompileResult = {};
-                for (let key in data["RawNameToCompileResult"]) {
-                    if (data["RawNameToCompileResult"].hasOwnProperty(key))
-                        this.rawNameToCompileResult[key] = data["RawNameToCompileResult"][key] ? CompileResult.fromJS(data["RawNameToCompileResult"][key]) : new CompileResult();
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): CompileResults {
-        data = typeof data === 'object' ? data : {};
-        let result = new CompileResults();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.rawNameToCompileResult) {
-            data["RawNameToCompileResult"] = {};
-            for (let key in this.rawNameToCompileResult) {
-                if (this.rawNameToCompileResult.hasOwnProperty(key))
-                    data["RawNameToCompileResult"][key] = this.rawNameToCompileResult[key];
-            }
-        }
-        return data;
-    }
-}
-
-export interface ICompileResults {
-    rawNameToCompileResult?: { [key: string]: CompileResult; } | undefined;
-}
-
-export abstract class UniqueJson implements IUniqueJson {
-
-    constructor(data?: IUniqueJson) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): UniqueJson {
-        data = typeof data === 'object' ? data : {};
-        throw new Error("The abstract class 'UniqueJson' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        return data;
-    }
-}
-
-export interface IUniqueJson {
-}
-
-export abstract class OperationParameters extends UniqueJson implements IOperationParameters {
-    isCachable?: boolean | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IOperationParameters) {
-        super(data);
-        this._discriminator = "OperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.isCachable = data["IsCachable"];
-        }
-    }
-
-    static fromJS(data: any): OperationParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "DataOperationParameters") {
-            throw new Error("The abstract class 'DataOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "ExampleOperationParameters") {
-            let result = new ExampleOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "HistogramOperationParameters") {
-            let result = new HistogramOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DistOperationParameters") {
-            throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "OptimizerOperationParameters") {
-            let result = new OptimizerOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RawDataOperationParameters") {
-            let result = new RawDataOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RecommenderOperationParameters") {
-            let result = new RecommenderOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CDFOperationParameters") {
-            let result = new CDFOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TestDistOperationParameters") {
-            throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "ChiSquaredTestOperationParameters") {
-            let result = new ChiSquaredTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "HypothesisTestParameters") {
-            throw new Error("The abstract class 'HypothesisTestParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "CorrelationTestOperationParameters") {
-            let result = new CorrelationTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "EmpiricalDistOperationParameters") {
-            let result = new EmpiricalDistOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "KSTestOperationParameters") {
-            let result = new KSTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "NewModelOperationParameters") {
-            let result = new NewModelOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "ModelOperationParameters") {
-            throw new Error("The abstract class 'ModelOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "RootMeanSquareTestOperationParameters") {
-            let result = new RootMeanSquareTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TTestOperationParameters") {
-            let result = new TTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FeatureImportanceOperationParameters") {
-            let result = new FeatureImportanceOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SampleOperationParameters") {
-            let result = new SampleOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AddComparisonParameters") {
-            let result = new AddComparisonParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "GetModelStateParameters") {
-            let result = new GetModelStateParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FrequentItemsetOperationParameters") {
-            let result = new FrequentItemsetOperationParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'OperationParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["IsCachable"] = this.isCachable;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IOperationParameters extends IUniqueJson {
-    isCachable?: boolean | undefined;
-}
-
-export abstract class DataOperationParameters extends OperationParameters implements IDataOperationParameters {
-    sampleStreamBlockSize?: number | undefined;
-    adapterName?: string | undefined;
-    isCachable?: boolean | undefined;
-
-    constructor(data?: IDataOperationParameters) {
-        super(data);
-        this._discriminator = "DataOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.sampleStreamBlockSize = data["SampleStreamBlockSize"];
-            this.adapterName = data["AdapterName"];
-            this.isCachable = data["IsCachable"];
-        }
-    }
-
-    static fromJS(data: any): DataOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "ExampleOperationParameters") {
-            let result = new ExampleOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "HistogramOperationParameters") {
-            let result = new HistogramOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DistOperationParameters") {
-            throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "OptimizerOperationParameters") {
-            let result = new OptimizerOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RawDataOperationParameters") {
-            let result = new RawDataOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RecommenderOperationParameters") {
-            let result = new RecommenderOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CDFOperationParameters") {
-            let result = new CDFOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TestDistOperationParameters") {
-            throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "EmpiricalDistOperationParameters") {
-            let result = new EmpiricalDistOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FeatureImportanceOperationParameters") {
-            let result = new FeatureImportanceOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SampleOperationParameters") {
-            let result = new SampleOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FrequentItemsetOperationParameters") {
-            let result = new FrequentItemsetOperationParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'DataOperationParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SampleStreamBlockSize"] = this.sampleStreamBlockSize;
-        data["AdapterName"] = this.adapterName;
-        data["IsCachable"] = this.isCachable;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDataOperationParameters extends IOperationParameters {
-    sampleStreamBlockSize?: number | undefined;
-    adapterName?: string | undefined;
-    isCachable?: boolean | undefined;
-}
-
-export class ExampleOperationParameters extends DataOperationParameters implements IExampleOperationParameters {
-    filter?: string | undefined;
-    attributeParameters?: AttributeParameters[] | undefined;
-    attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
-    dummyValue?: number | undefined;
-    exampleType?: string | undefined;
-
-    constructor(data?: IExampleOperationParameters) {
-        super(data);
-        this._discriminator = "ExampleOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.filter = data["Filter"];
-            if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
-                this.attributeParameters = [];
-                for (let item of data["AttributeParameters"])
-                    this.attributeParameters.push(AttributeParameters.fromJS(item));
-            }
-            if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) {
-                this.attributeCodeParameters = [];
-                for (let item of data["AttributeCodeParameters"])
-                    this.attributeCodeParameters.push(AttributeCaclculatedParameters.fromJS(item));
-            }
-            this.dummyValue = data["DummyValue"];
-            this.exampleType = data["ExampleType"];
-        }
-    }
-
-    static fromJS(data: any): ExampleOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new ExampleOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Filter"] = this.filter;
-        if (this.attributeParameters && this.attributeParameters.constructor === Array) {
-            data["AttributeParameters"] = [];
-            for (let item of this.attributeParameters)
-                data["AttributeParameters"].push(item.toJSON());
-        }
-        if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) {
-            data["AttributeCodeParameters"] = [];
-            for (let item of this.attributeCodeParameters)
-                data["AttributeCodeParameters"].push(item.toJSON());
-        }
-        data["DummyValue"] = this.dummyValue;
-        data["ExampleType"] = this.exampleType;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IExampleOperationParameters extends IDataOperationParameters {
-    filter?: string | undefined;
-    attributeParameters?: AttributeParameters[] | undefined;
-    attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
-    dummyValue?: number | undefined;
-    exampleType?: string | undefined;
-}
-
-export abstract class DistOperationParameters extends DataOperationParameters implements IDistOperationParameters {
-    filter?: string | undefined;
-    attributeCalculatedParameters?: AttributeCaclculatedParameters[] | undefined;
-
-    constructor(data?: IDistOperationParameters) {
-        super(data);
-        this._discriminator = "DistOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.filter = data["Filter"];
-            if (data["AttributeCalculatedParameters"] && data["AttributeCalculatedParameters"].constructor === Array) {
-                this.attributeCalculatedParameters = [];
-                for (let item of data["AttributeCalculatedParameters"])
-                    this.attributeCalculatedParameters.push(AttributeCaclculatedParameters.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): DistOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "HistogramOperationParameters") {
-            let result = new HistogramOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "OptimizerOperationParameters") {
-            let result = new OptimizerOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RawDataOperationParameters") {
-            let result = new RawDataOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CDFOperationParameters") {
-            let result = new CDFOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TestDistOperationParameters") {
-            throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "EmpiricalDistOperationParameters") {
-            let result = new EmpiricalDistOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FeatureImportanceOperationParameters") {
-            let result = new FeatureImportanceOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SampleOperationParameters") {
-            let result = new SampleOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FrequentItemsetOperationParameters") {
-            let result = new FrequentItemsetOperationParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Filter"] = this.filter;
-        if (this.attributeCalculatedParameters && this.attributeCalculatedParameters.constructor === Array) {
-            data["AttributeCalculatedParameters"] = [];
-            for (let item of this.attributeCalculatedParameters)
-                data["AttributeCalculatedParameters"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDistOperationParameters extends IDataOperationParameters {
-    filter?: string | undefined;
-    attributeCalculatedParameters?: AttributeCaclculatedParameters[] | undefined;
-}
-
-export class HistogramOperationParameters extends DistOperationParameters implements IHistogramOperationParameters {
-    sortPerBinAggregateParameter?: AggregateParameters | undefined;
-    binningParameters?: BinningParameters[] | undefined;
-    perBinAggregateParameters?: AggregateParameters[] | undefined;
-    globalAggregateParameters?: AggregateParameters[] | undefined;
-    brushes?: string[] | undefined;
-    enableBrushComputation?: boolean | undefined;
-    degreeOfParallism?: number | undefined;
-
-    constructor(data?: IHistogramOperationParameters) {
-        super(data);
-        this._discriminator = "HistogramOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.sortPerBinAggregateParameter = data["SortPerBinAggregateParameter"] ? AggregateParameters.fromJS(data["SortPerBinAggregateParameter"]) : <any>undefined;
-            if (data["BinningParameters"] && data["BinningParameters"].constructor === Array) {
-                this.binningParameters = [];
-                for (let item of data["BinningParameters"])
-                    this.binningParameters.push(BinningParameters.fromJS(item));
-            }
-            if (data["PerBinAggregateParameters"] && data["PerBinAggregateParameters"].constructor === Array) {
-                this.perBinAggregateParameters = [];
-                for (let item of data["PerBinAggregateParameters"])
-                    this.perBinAggregateParameters.push(AggregateParameters.fromJS(item));
-            }
-            if (data["GlobalAggregateParameters"] && data["GlobalAggregateParameters"].constructor === Array) {
-                this.globalAggregateParameters = [];
-                for (let item of data["GlobalAggregateParameters"])
-                    this.globalAggregateParameters.push(AggregateParameters.fromJS(item));
-            }
-            if (data["Brushes"] && data["Brushes"].constructor === Array) {
-                this.brushes = [];
-                for (let item of data["Brushes"])
-                    this.brushes.push(item);
-            }
-            this.enableBrushComputation = data["EnableBrushComputation"];
-            this.degreeOfParallism = data["DegreeOfParallism"];
-        }
-    }
-
-    static fromJS(data: any): HistogramOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new HistogramOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SortPerBinAggregateParameter"] = this.sortPerBinAggregateParameter ? this.sortPerBinAggregateParameter.toJSON() : <any>undefined;
-        if (this.binningParameters && this.binningParameters.constructor === Array) {
-            data["BinningParameters"] = [];
-            for (let item of this.binningParameters)
-                data["BinningParameters"].push(item.toJSON());
-        }
-        if (this.perBinAggregateParameters && this.perBinAggregateParameters.constructor === Array) {
-            data["PerBinAggregateParameters"] = [];
-            for (let item of this.perBinAggregateParameters)
-                data["PerBinAggregateParameters"].push(item.toJSON());
-        }
-        if (this.globalAggregateParameters && this.globalAggregateParameters.constructor === Array) {
-            data["GlobalAggregateParameters"] = [];
-            for (let item of this.globalAggregateParameters)
-                data["GlobalAggregateParameters"].push(item.toJSON());
-        }
-        if (this.brushes && this.brushes.constructor === Array) {
-            data["Brushes"] = [];
-            for (let item of this.brushes)
-                data["Brushes"].push(item);
-        }
-        data["EnableBrushComputation"] = this.enableBrushComputation;
-        data["DegreeOfParallism"] = this.degreeOfParallism;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IHistogramOperationParameters extends IDistOperationParameters {
-    sortPerBinAggregateParameter?: AggregateParameters | undefined;
-    binningParameters?: BinningParameters[] | undefined;
-    perBinAggregateParameters?: AggregateParameters[] | undefined;
-    globalAggregateParameters?: AggregateParameters[] | undefined;
-    brushes?: string[] | undefined;
-    enableBrushComputation?: boolean | undefined;
-    degreeOfParallism?: number | undefined;
-}
-
-export class OptimizerOperationParameters extends DistOperationParameters implements IOptimizerOperationParameters {
-    taskType?: TaskType | undefined;
-    taskSubType?: TaskSubType | undefined;
-    metricType?: MetricType | undefined;
-    labelAttribute?: AttributeParameters | undefined;
-    featureAttributes?: AttributeParameters[] | undefined;
-    requiredPrimitives?: string[] | undefined;
-    pythonFilter?: string | undefined;
-    trainFilter?: string | undefined;
-    pythonTrainFilter?: string | undefined;
-    testFilter?: string | undefined;
-    pythonTestFilter?: string | undefined;
-
-    constructor(data?: IOptimizerOperationParameters) {
-        super(data);
-        this._discriminator = "OptimizerOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.taskType = data["TaskType"];
-            this.taskSubType = data["TaskSubType"];
-            this.metricType = data["MetricType"];
-            this.labelAttribute = data["LabelAttribute"] ? AttributeParameters.fromJS(data["LabelAttribute"]) : <any>undefined;
-            if (data["FeatureAttributes"] && data["FeatureAttributes"].constructor === Array) {
-                this.featureAttributes = [];
-                for (let item of data["FeatureAttributes"])
-                    this.featureAttributes.push(AttributeParameters.fromJS(item));
-            }
-            if (data["RequiredPrimitives"] && data["RequiredPrimitives"].constructor === Array) {
-                this.requiredPrimitives = [];
-                for (let item of data["RequiredPrimitives"])
-                    this.requiredPrimitives.push(item);
-            }
-            this.pythonFilter = data["PythonFilter"];
-            this.trainFilter = data["TrainFilter"];
-            this.pythonTrainFilter = data["PythonTrainFilter"];
-            this.testFilter = data["TestFilter"];
-            this.pythonTestFilter = data["PythonTestFilter"];
-        }
-    }
-
-    static fromJS(data: any): OptimizerOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new OptimizerOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["TaskType"] = this.taskType;
-        data["TaskSubType"] = this.taskSubType;
-        data["MetricType"] = this.metricType;
-        data["LabelAttribute"] = this.labelAttribute ? this.labelAttribute.toJSON() : <any>undefined;
-        if (this.featureAttributes && this.featureAttributes.constructor === Array) {
-            data["FeatureAttributes"] = [];
-            for (let item of this.featureAttributes)
-                data["FeatureAttributes"].push(item.toJSON());
-        }
-        if (this.requiredPrimitives && this.requiredPrimitives.constructor === Array) {
-            data["RequiredPrimitives"] = [];
-            for (let item of this.requiredPrimitives)
-                data["RequiredPrimitives"].push(item);
-        }
-        data["PythonFilter"] = this.pythonFilter;
-        data["TrainFilter"] = this.trainFilter;
-        data["PythonTrainFilter"] = this.pythonTrainFilter;
-        data["TestFilter"] = this.testFilter;
-        data["PythonTestFilter"] = this.pythonTestFilter;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IOptimizerOperationParameters extends IDistOperationParameters {
-    taskType?: TaskType | undefined;
-    taskSubType?: TaskSubType | undefined;
-    metricType?: MetricType | undefined;
-    labelAttribute?: AttributeParameters | undefined;
-    featureAttributes?: AttributeParameters[] | undefined;
-    requiredPrimitives?: string[] | undefined;
-    pythonFilter?: string | undefined;
-    trainFilter?: string | undefined;
-    pythonTrainFilter?: string | undefined;
-    testFilter?: string | undefined;
-    pythonTestFilter?: string | undefined;
-}
-
-export enum TaskType {
-    Undefined = 0,
-    Classification = 1,
-    Regression = 2,
-    Clustering = 3,
-    LinkPrediction = 4,
-    VertexNomination = 5,
-    CommunityDetection = 6,
-    GraphClustering = 7,
-    GraphMatching = 8,
-    TimeSeriesForecasting = 9,
-    CollaborativeFiltering = 10,
-}
-
-export enum TaskSubType {
-    Undefined = 0,
-    None = 1,
-    Binary = 2,
-    Multiclass = 3,
-    Multilabel = 4,
-    Univariate = 5,
-    Multivariate = 6,
-    Overlapping = 7,
-    Nonoverlapping = 8,
-}
-
-export enum MetricType {
-    MetricUndefined = 0,
-    Accuracy = 1,
-    Precision = 2,
-    Recall = 3,
-    F1 = 4,
-    F1Micro = 5,
-    F1Macro = 6,
-    RocAuc = 7,
-    RocAucMicro = 8,
-    RocAucMacro = 9,
-    MeanSquaredError = 10,
-    RootMeanSquaredError = 11,
-    RootMeanSquaredErrorAvg = 12,
-    MeanAbsoluteError = 13,
-    RSquared = 14,
-    NormalizedMutualInformation = 15,
-    JaccardSimilarityScore = 16,
-    PrecisionAtTopK = 17,
-    ObjectDetectionAveragePrecision = 18,
-    Loss = 100,
-}
-
-export class RawDataOperationParameters extends DistOperationParameters implements IRawDataOperationParameters {
-    sortUpRawName?: string | undefined;
-    sortDownRawName?: string | undefined;
-    numRecords?: number | undefined;
-    binningParameters?: BinningParameters[] | undefined;
-    brushes?: string[] | undefined;
-    enableBrushComputation?: boolean | undefined;
-
-    constructor(data?: IRawDataOperationParameters) {
-        super(data);
-        this._discriminator = "RawDataOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.sortUpRawName = data["SortUpRawName"];
-            this.sortDownRawName = data["SortDownRawName"];
-            this.numRecords = data["NumRecords"];
-            if (data["BinningParameters"] && data["BinningParameters"].constructor === Array) {
-                this.binningParameters = [];
-                for (let item of data["BinningParameters"])
-                    this.binningParameters.push(BinningParameters.fromJS(item));
-            }
-            if (data["Brushes"] && data["Brushes"].constructor === Array) {
-                this.brushes = [];
-                for (let item of data["Brushes"])
-                    this.brushes.push(item);
-            }
-            this.enableBrushComputation = data["EnableBrushComputation"];
-        }
-    }
-
-    static fromJS(data: any): RawDataOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new RawDataOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SortUpRawName"] = this.sortUpRawName;
-        data["SortDownRawName"] = this.sortDownRawName;
-        data["NumRecords"] = this.numRecords;
-        if (this.binningParameters && this.binningParameters.constructor === Array) {
-            data["BinningParameters"] = [];
-            for (let item of this.binningParameters)
-                data["BinningParameters"].push(item.toJSON());
-        }
-        if (this.brushes && this.brushes.constructor === Array) {
-            data["Brushes"] = [];
-            for (let item of this.brushes)
-                data["Brushes"].push(item);
-        }
-        data["EnableBrushComputation"] = this.enableBrushComputation;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRawDataOperationParameters extends IDistOperationParameters {
-    sortUpRawName?: string | undefined;
-    sortDownRawName?: string | undefined;
-    numRecords?: number | undefined;
-    binningParameters?: BinningParameters[] | undefined;
-    brushes?: string[] | undefined;
-    enableBrushComputation?: boolean | undefined;
-}
-
-export class RecommenderOperationParameters extends DataOperationParameters implements IRecommenderOperationParameters {
-    target?: HistogramOperationParameters | undefined;
-    budget?: number | undefined;
-    modelId?: ModelId | undefined;
-    includeAttributeParameters?: AttributeParameters[] | undefined;
-    excludeAttributeParameters?: AttributeParameters[] | undefined;
-    riskControlType?: RiskControlType | undefined;
-
-    constructor(data?: IRecommenderOperationParameters) {
-        super(data);
-        this._discriminator = "RecommenderOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.target = data["Target"] ? HistogramOperationParameters.fromJS(data["Target"]) : <any>undefined;
-            this.budget = data["Budget"];
-            this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
-            if (data["IncludeAttributeParameters"] && data["IncludeAttributeParameters"].constructor === Array) {
-                this.includeAttributeParameters = [];
-                for (let item of data["IncludeAttributeParameters"])
-                    this.includeAttributeParameters.push(AttributeParameters.fromJS(item));
-            }
-            if (data["ExcludeAttributeParameters"] && data["ExcludeAttributeParameters"].constructor === Array) {
-                this.excludeAttributeParameters = [];
-                for (let item of data["ExcludeAttributeParameters"])
-                    this.excludeAttributeParameters.push(AttributeParameters.fromJS(item));
-            }
-            this.riskControlType = data["RiskControlType"];
-        }
-    }
-
-    static fromJS(data: any): RecommenderOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new RecommenderOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Target"] = this.target ? this.target.toJSON() : <any>undefined;
-        data["Budget"] = this.budget;
-        data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
-        if (this.includeAttributeParameters && this.includeAttributeParameters.constructor === Array) {
-            data["IncludeAttributeParameters"] = [];
-            for (let item of this.includeAttributeParameters)
-                data["IncludeAttributeParameters"].push(item.toJSON());
-        }
-        if (this.excludeAttributeParameters && this.excludeAttributeParameters.constructor === Array) {
-            data["ExcludeAttributeParameters"] = [];
-            for (let item of this.excludeAttributeParameters)
-                data["ExcludeAttributeParameters"].push(item.toJSON());
-        }
-        data["RiskControlType"] = this.riskControlType;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRecommenderOperationParameters extends IDataOperationParameters {
-    target?: HistogramOperationParameters | undefined;
-    budget?: number | undefined;
-    modelId?: ModelId | undefined;
-    includeAttributeParameters?: AttributeParameters[] | undefined;
-    excludeAttributeParameters?: AttributeParameters[] | undefined;
-    riskControlType?: RiskControlType | undefined;
-}
-
-export class ModelId implements IModelId {
-    value?: string | undefined;
-
-    constructor(data?: IModelId) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): ModelId {
-        data = typeof data === 'object' ? data : {};
-        let result = new ModelId();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        return data;
-    }
-}
-
-export interface IModelId {
-    value?: string | undefined;
-}
-
-export enum RiskControlType {
-    PCER = 0,
-    Bonferroni = 1,
-    AdaBonferroni = 2,
-    HolmBonferroni = 3,
-    BHFDR = 4,
-    SeqFDR = 5,
-    AlphaFDR = 6,
-    BestFootForward = 7,
-    BetaFarsighted = 8,
-    BetaFarsightedWithSupport = 9,
-    GammaFixed = 10,
-    DeltaHopeful = 11,
-    EpsilonHybrid = 12,
-    EpsilonHybridWithoutSupport = 13,
-    PsiSupport = 14,
-    ZetaDynamic = 15,
-    Unknown = 16,
-}
-
-export abstract class TestDistOperationParameters extends DistOperationParameters implements ITestDistOperationParameters {
-    attributeParameters?: AttributeParameters[] | undefined;
-
-    constructor(data?: ITestDistOperationParameters) {
-        super(data);
-        this._discriminator = "TestDistOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
-                this.attributeParameters = [];
-                for (let item of data["AttributeParameters"])
-                    this.attributeParameters.push(AttributeParameters.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): TestDistOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "CDFOperationParameters") {
-            let result = new CDFOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "EmpiricalDistOperationParameters") {
-            let result = new EmpiricalDistOperationParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.attributeParameters && this.attributeParameters.constructor === Array) {
-            data["AttributeParameters"] = [];
-            for (let item of this.attributeParameters)
-                data["AttributeParameters"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ITestDistOperationParameters extends IDistOperationParameters {
-    attributeParameters?: AttributeParameters[] | undefined;
-}
-
-export class CDFOperationParameters extends TestDistOperationParameters implements ICDFOperationParameters {
-
-    constructor(data?: ICDFOperationParameters) {
-        super(data);
-        this._discriminator = "CDFOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): CDFOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new CDFOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ICDFOperationParameters extends ITestDistOperationParameters {
-}
-
-export abstract class HypothesisTestParameters extends OperationParameters implements IHypothesisTestParameters {
-    childOperationParameters?: OperationParameters[] | undefined;
-    isCachable?: boolean | undefined;
-
-    constructor(data?: IHypothesisTestParameters) {
-        super(data);
-        this._discriminator = "HypothesisTestParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["ChildOperationParameters"] && data["ChildOperationParameters"].constructor === Array) {
-                this.childOperationParameters = [];
-                for (let item of data["ChildOperationParameters"])
-                    this.childOperationParameters.push(OperationParameters.fromJS(item));
-            }
-            this.isCachable = data["IsCachable"];
-        }
-    }
-
-    static fromJS(data: any): HypothesisTestParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "ChiSquaredTestOperationParameters") {
-            let result = new ChiSquaredTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CorrelationTestOperationParameters") {
-            let result = new CorrelationTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "KSTestOperationParameters") {
-            let result = new KSTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RootMeanSquareTestOperationParameters") {
-            let result = new RootMeanSquareTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TTestOperationParameters") {
-            let result = new TTestOperationParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'HypothesisTestParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.childOperationParameters && this.childOperationParameters.constructor === Array) {
-            data["ChildOperationParameters"] = [];
-            for (let item of this.childOperationParameters)
-                data["ChildOperationParameters"].push(item.toJSON());
-        }
-        data["IsCachable"] = this.isCachable;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IHypothesisTestParameters extends IOperationParameters {
-    childOperationParameters?: OperationParameters[] | undefined;
-    isCachable?: boolean | undefined;
-}
-
-export class ChiSquaredTestOperationParameters extends HypothesisTestParameters implements IChiSquaredTestOperationParameters {
-
-    constructor(data?: IChiSquaredTestOperationParameters) {
-        super(data);
-        this._discriminator = "ChiSquaredTestOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): ChiSquaredTestOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new ChiSquaredTestOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IChiSquaredTestOperationParameters extends IHypothesisTestParameters {
-}
-
-export class CorrelationTestOperationParameters extends HypothesisTestParameters implements ICorrelationTestOperationParameters {
-
-    constructor(data?: ICorrelationTestOperationParameters) {
-        super(data);
-        this._discriminator = "CorrelationTestOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): CorrelationTestOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new CorrelationTestOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ICorrelationTestOperationParameters extends IHypothesisTestParameters {
-}
-
-export class SubmitProblemParameters implements ISubmitProblemParameters {
-    id?: string | undefined;
-
-    constructor(data?: ISubmitProblemParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.id = data["Id"];
-        }
-    }
-
-    static fromJS(data: any): SubmitProblemParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new SubmitProblemParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        return data;
-    }
-}
-
-export interface ISubmitProblemParameters {
-    id?: string | undefined;
-}
-
-export class SpecifyProblemParameters implements ISpecifyProblemParameters {
-    id?: string | undefined;
-    userComment?: string | undefined;
-    optimizerOperationParameters?: OptimizerOperationParameters | undefined;
-
-    constructor(data?: ISpecifyProblemParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.id = data["Id"];
-            this.userComment = data["UserComment"];
-            this.optimizerOperationParameters = data["OptimizerOperationParameters"] ? OptimizerOperationParameters.fromJS(data["OptimizerOperationParameters"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): SpecifyProblemParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new SpecifyProblemParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        data["UserComment"] = this.userComment;
-        data["OptimizerOperationParameters"] = this.optimizerOperationParameters ? this.optimizerOperationParameters.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface ISpecifyProblemParameters {
-    id?: string | undefined;
-    userComment?: string | undefined;
-    optimizerOperationParameters?: OptimizerOperationParameters | undefined;
-}
-
-export class EmpiricalDistOperationParameters extends TestDistOperationParameters implements IEmpiricalDistOperationParameters {
-    keepSamples?: boolean | undefined;
-
-    constructor(data?: IEmpiricalDistOperationParameters) {
-        super(data);
-        this._discriminator = "EmpiricalDistOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.keepSamples = data["KeepSamples"];
-        }
-    }
-
-    static fromJS(data: any): EmpiricalDistOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new EmpiricalDistOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["KeepSamples"] = this.keepSamples;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IEmpiricalDistOperationParameters extends ITestDistOperationParameters {
-    keepSamples?: boolean | undefined;
-}
-
-export class KSTestOperationParameters extends HypothesisTestParameters implements IKSTestOperationParameters {
-
-    constructor(data?: IKSTestOperationParameters) {
-        super(data);
-        this._discriminator = "KSTestOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): KSTestOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new KSTestOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IKSTestOperationParameters extends IHypothesisTestParameters {
-}
-
-export abstract class ModelOperationParameters extends OperationParameters implements IModelOperationParameters {
-
-    constructor(data?: IModelOperationParameters) {
-        super(data);
-        this._discriminator = "ModelOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): ModelOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "NewModelOperationParameters") {
-            let result = new NewModelOperationParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AddComparisonParameters") {
-            let result = new AddComparisonParameters();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "GetModelStateParameters") {
-            let result = new GetModelStateParameters();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'ModelOperationParameters' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IModelOperationParameters extends IOperationParameters {
-}
-
-export class NewModelOperationParameters extends ModelOperationParameters implements INewModelOperationParameters {
-    riskControlTypes?: RiskControlType[] | undefined;
-    alpha?: number | undefined;
-    alphaInvestParameter?: AlphaInvestParameter | undefined;
-
-    constructor(data?: INewModelOperationParameters) {
-        super(data);
-        this._discriminator = "NewModelOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["RiskControlTypes"] && data["RiskControlTypes"].constructor === Array) {
-                this.riskControlTypes = [];
-                for (let item of data["RiskControlTypes"])
-                    this.riskControlTypes.push(item);
-            }
-            this.alpha = data["Alpha"];
-            this.alphaInvestParameter = data["AlphaInvestParameter"] ? AlphaInvestParameter.fromJS(data["AlphaInvestParameter"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): NewModelOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new NewModelOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.riskControlTypes && this.riskControlTypes.constructor === Array) {
-            data["RiskControlTypes"] = [];
-            for (let item of this.riskControlTypes)
-                data["RiskControlTypes"].push(item);
-        }
-        data["Alpha"] = this.alpha;
-        data["AlphaInvestParameter"] = this.alphaInvestParameter ? this.alphaInvestParameter.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface INewModelOperationParameters extends IModelOperationParameters {
-    riskControlTypes?: RiskControlType[] | undefined;
-    alpha?: number | undefined;
-    alphaInvestParameter?: AlphaInvestParameter | undefined;
-}
-
-export class AlphaInvestParameter extends UniqueJson implements IAlphaInvestParameter {
-    beta?: number | undefined;
-    gamma?: number | undefined;
-    delta?: number | undefined;
-    epsilon?: number | undefined;
-    windowSize?: number | undefined;
-    psi?: number | undefined;
-
-    constructor(data?: IAlphaInvestParameter) {
-        super(data);
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.beta = data["Beta"];
-            this.gamma = data["Gamma"];
-            this.delta = data["Delta"];
-            this.epsilon = data["Epsilon"];
-            this.windowSize = data["WindowSize"];
-            this.psi = data["Psi"];
-        }
-    }
-
-    static fromJS(data: any): AlphaInvestParameter {
-        data = typeof data === 'object' ? data : {};
-        let result = new AlphaInvestParameter();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Beta"] = this.beta;
-        data["Gamma"] = this.gamma;
-        data["Delta"] = this.delta;
-        data["Epsilon"] = this.epsilon;
-        data["WindowSize"] = this.windowSize;
-        data["Psi"] = this.psi;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAlphaInvestParameter extends IUniqueJson {
-    beta?: number | undefined;
-    gamma?: number | undefined;
-    delta?: number | undefined;
-    epsilon?: number | undefined;
-    windowSize?: number | undefined;
-    psi?: number | undefined;
-}
-
-export class RootMeanSquareTestOperationParameters extends HypothesisTestParameters implements IRootMeanSquareTestOperationParameters {
-    seeded?: boolean | undefined;
-    pValueConvergenceThreshold?: number | undefined;
-    maxSimulationBatchCount?: number | undefined;
-    perBatchSimulationCount?: number | undefined;
-
-    constructor(data?: IRootMeanSquareTestOperationParameters) {
-        super(data);
-        this._discriminator = "RootMeanSquareTestOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.seeded = data["Seeded"];
-            this.pValueConvergenceThreshold = data["PValueConvergenceThreshold"];
-            this.maxSimulationBatchCount = data["MaxSimulationBatchCount"];
-            this.perBatchSimulationCount = data["PerBatchSimulationCount"];
-        }
-    }
-
-    static fromJS(data: any): RootMeanSquareTestOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new RootMeanSquareTestOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Seeded"] = this.seeded;
-        data["PValueConvergenceThreshold"] = this.pValueConvergenceThreshold;
-        data["MaxSimulationBatchCount"] = this.maxSimulationBatchCount;
-        data["PerBatchSimulationCount"] = this.perBatchSimulationCount;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRootMeanSquareTestOperationParameters extends IHypothesisTestParameters {
-    seeded?: boolean | undefined;
-    pValueConvergenceThreshold?: number | undefined;
-    maxSimulationBatchCount?: number | undefined;
-    perBatchSimulationCount?: number | undefined;
-}
-
-export class TTestOperationParameters extends HypothesisTestParameters implements ITTestOperationParameters {
-
-    constructor(data?: ITTestOperationParameters) {
-        super(data);
-        this._discriminator = "TTestOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): TTestOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new TTestOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ITTestOperationParameters extends IHypothesisTestParameters {
-}
-
-export enum EffectSize {
-    Small = 1,
-    Meduim = 2,
-    Large = 4,
-}
-
-export abstract class Result extends UniqueJson implements IResult {
-    progress?: number | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IResult) {
-        super(data);
-        this._discriminator = "Result";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.progress = data["Progress"];
-        }
-    }
-
-    static fromJS(data: any): Result {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "ErrorResult") {
-            let result = new ErrorResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "HistogramResult") {
-            let result = new HistogramResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DistResult") {
-            throw new Error("The abstract class 'DistResult' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "ModelWealthResult") {
-            let result = new ModelWealthResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "HypothesisTestResult") {
-            throw new Error("The abstract class 'HypothesisTestResult' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "ModelOperationResult") {
-            throw new Error("The abstract class 'ModelOperationResult' cannot be instantiated.");
-        }
-        if (data["discriminator"] === "RecommenderResult") {
-            let result = new RecommenderResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "Decision") {
-            let result = new Decision();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "OptimizerResult") {
-            let result = new OptimizerResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "ExampleResult") {
-            let result = new ExampleResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "NewModelOperationResult") {
-            let result = new NewModelOperationResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AddComparisonResult") {
-            let result = new AddComparisonResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "GetModelStateResult") {
-            let result = new GetModelStateResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FeatureImportanceResult") {
-            let result = new FeatureImportanceResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RawDataResult") {
-            let result = new RawDataResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SampleResult") {
-            let result = new SampleResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CDFResult") {
-            let result = new CDFResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "ChiSquaredTestResult") {
-            let result = new ChiSquaredTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CorrelationTestResult") {
-            let result = new CorrelationTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "EmpiricalDistResult") {
-            let result = new EmpiricalDistResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "KSTestResult") {
-            let result = new KSTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RootMeanSquareTestResult") {
-            let result = new RootMeanSquareTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TTestResult") {
-            let result = new TTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "FrequentItemsetResult") {
-            let result = new FrequentItemsetResult();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'Result' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["Progress"] = this.progress;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IResult extends IUniqueJson {
-    progress?: number | undefined;
-}
-
-export class ErrorResult extends Result implements IErrorResult {
-    message?: string | undefined;
-
-    constructor(data?: IErrorResult) {
-        super(data);
-        this._discriminator = "ErrorResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.message = data["Message"];
-        }
-    }
-
-    static fromJS(data: any): ErrorResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new ErrorResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Message"] = this.message;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IErrorResult extends IResult {
-    message?: string | undefined;
-}
-
-export abstract class DistResult extends Result implements IDistResult {
-    sampleSize?: number | undefined;
-    populationSize?: number | undefined;
-
-    constructor(data?: IDistResult) {
-        super(data);
-        this._discriminator = "DistResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.sampleSize = data["SampleSize"];
-            this.populationSize = data["PopulationSize"];
-        }
-    }
-
-    static fromJS(data: any): DistResult {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "HistogramResult") {
-            let result = new HistogramResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RawDataResult") {
-            let result = new RawDataResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SampleResult") {
-            let result = new SampleResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CDFResult") {
-            let result = new CDFResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "EmpiricalDistResult") {
-            let result = new EmpiricalDistResult();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'DistResult' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SampleSize"] = this.sampleSize;
-        data["PopulationSize"] = this.populationSize;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDistResult extends IResult {
-    sampleSize?: number | undefined;
-    populationSize?: number | undefined;
-}
-
-export class HistogramResult extends DistResult implements IHistogramResult {
-    aggregateResults?: AggregateResult[][] | undefined;
-    isEmpty?: boolean | undefined;
-    brushes?: Brush[] | undefined;
-    binRanges?: BinRange[] | undefined;
-    aggregateParameters?: AggregateParameters[] | undefined;
-    nullValueCount?: number | undefined;
-    bins?: { [key: string]: Bin; } | undefined;
-
-    constructor(data?: IHistogramResult) {
-        super(data);
-        this._discriminator = "HistogramResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["AggregateResults"] && data["AggregateResults"].constructor === Array) {
-                this.aggregateResults = [];
-                for (let item of data["AggregateResults"])
-                    this.aggregateResults.push(item);
-            }
-            this.isEmpty = data["IsEmpty"];
-            if (data["Brushes"] && data["Brushes"].constructor === Array) {
-                this.brushes = [];
-                for (let item of data["Brushes"])
-                    this.brushes.push(Brush.fromJS(item));
-            }
-            if (data["BinRanges"] && data["BinRanges"].constructor === Array) {
-                this.binRanges = [];
-                for (let item of data["BinRanges"])
-                    this.binRanges.push(BinRange.fromJS(item));
-            }
-            if (data["AggregateParameters"] && data["AggregateParameters"].constructor === Array) {
-                this.aggregateParameters = [];
-                for (let item of data["AggregateParameters"])
-                    this.aggregateParameters.push(AggregateParameters.fromJS(item));
-            }
-            this.nullValueCount = data["NullValueCount"];
-            if (data["Bins"]) {
-                this.bins = {};
-                for (let key in data["Bins"]) {
-                    if (data["Bins"].hasOwnProperty(key))
-                        this.bins[key] = data["Bins"][key] ? Bin.fromJS(data["Bins"][key]) : new Bin();
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): HistogramResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new HistogramResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.aggregateResults && this.aggregateResults.constructor === Array) {
-            data["AggregateResults"] = [];
-            for (let item of this.aggregateResults)
-                data["AggregateResults"].push(item);
-        }
-        data["IsEmpty"] = this.isEmpty;
-        if (this.brushes && this.brushes.constructor === Array) {
-            data["Brushes"] = [];
-            for (let item of this.brushes)
-                data["Brushes"].push(item.toJSON());
-        }
-        if (this.binRanges && this.binRanges.constructor === Array) {
-            data["BinRanges"] = [];
-            for (let item of this.binRanges)
-                data["BinRanges"].push(item.toJSON());
-        }
-        if (this.aggregateParameters && this.aggregateParameters.constructor === Array) {
-            data["AggregateParameters"] = [];
-            for (let item of this.aggregateParameters)
-                data["AggregateParameters"].push(item.toJSON());
-        }
-        data["NullValueCount"] = this.nullValueCount;
-        if (this.bins) {
-            data["Bins"] = {};
-            for (let key in this.bins) {
-                if (this.bins.hasOwnProperty(key))
-                    data["Bins"][key] = this.bins[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IHistogramResult extends IDistResult {
-    aggregateResults?: AggregateResult[][] | undefined;
-    isEmpty?: boolean | undefined;
-    brushes?: Brush[] | undefined;
-    binRanges?: BinRange[] | undefined;
-    aggregateParameters?: AggregateParameters[] | undefined;
-    nullValueCount?: number | undefined;
-    bins?: { [key: string]: Bin; } | undefined;
-}
-
-export abstract class AggregateResult implements IAggregateResult {
-    hasResult?: boolean | undefined;
-    n?: number | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IAggregateResult) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "AggregateResult";
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.hasResult = data["HasResult"];
-            this.n = data["N"];
-        }
-    }
-
-    static fromJS(data: any): AggregateResult | undefined {
-        if (data === null || data === undefined) {
-            return undefined;
-        }
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "MarginAggregateResult") {
-            let result = new MarginAggregateResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DoubleValueAggregateResult") {
-            let result = new DoubleValueAggregateResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PointsAggregateResult") {
-            let result = new PointsAggregateResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SumEstimationAggregateResult") {
-            let result = new SumEstimationAggregateResult();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'AggregateResult' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["HasResult"] = this.hasResult;
-        data["N"] = this.n;
-        return data;
-    }
-}
-
-export interface IAggregateResult {
-    hasResult?: boolean | undefined;
-    n?: number | undefined;
-}
-
-export class MarginAggregateResult extends AggregateResult implements IMarginAggregateResult {
-    margin?: number | undefined;
-    absolutMargin?: number | undefined;
-    sumOfSquare?: number | undefined;
-    sampleStandardDeviation?: number | undefined;
-    mean?: number | undefined;
-    ex?: number | undefined;
-    ex2?: number | undefined;
-    variance?: number | undefined;
-
-    constructor(data?: IMarginAggregateResult) {
-        super(data);
-        this._discriminator = "MarginAggregateResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.margin = data["Margin"];
-            this.absolutMargin = data["AbsolutMargin"];
-            this.sumOfSquare = data["SumOfSquare"];
-            this.sampleStandardDeviation = data["SampleStandardDeviation"];
-            this.mean = data["Mean"];
-            this.ex = data["Ex"];
-            this.ex2 = data["Ex2"];
-            this.variance = data["Variance"];
-        }
-    }
-
-    static fromJS(data: any): MarginAggregateResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new MarginAggregateResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Margin"] = this.margin;
-        data["AbsolutMargin"] = this.absolutMargin;
-        data["SumOfSquare"] = this.sumOfSquare;
-        data["SampleStandardDeviation"] = this.sampleStandardDeviation;
-        data["Mean"] = this.mean;
-        data["Ex"] = this.ex;
-        data["Ex2"] = this.ex2;
-        data["Variance"] = this.variance;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IMarginAggregateResult extends IAggregateResult {
-    margin?: number | undefined;
-    absolutMargin?: number | undefined;
-    sumOfSquare?: number | undefined;
-    sampleStandardDeviation?: number | undefined;
-    mean?: number | undefined;
-    ex?: number | undefined;
-    ex2?: number | undefined;
-    variance?: number | undefined;
-}
-
-export class DoubleValueAggregateResult extends AggregateResult implements IDoubleValueAggregateResult {
-    result?: number | undefined;
-    temporaryResult?: number | undefined;
-
-    constructor(data?: IDoubleValueAggregateResult) {
-        super(data);
-        this._discriminator = "DoubleValueAggregateResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.result = data["Result"];
-            this.temporaryResult = data["TemporaryResult"];
-        }
-    }
-
-    static fromJS(data: any): DoubleValueAggregateResult {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "SumEstimationAggregateResult") {
-            let result = new SumEstimationAggregateResult();
-            result.init(data);
-            return result;
-        }
-        let result = new DoubleValueAggregateResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Result"] = this.result;
-        data["TemporaryResult"] = this.temporaryResult;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDoubleValueAggregateResult extends IAggregateResult {
-    result?: number | undefined;
-    temporaryResult?: number | undefined;
-}
-
-export class PointsAggregateResult extends AggregateResult implements IPointsAggregateResult {
-    points?: Point[] | undefined;
-
-    constructor(data?: IPointsAggregateResult) {
-        super(data);
-        this._discriminator = "PointsAggregateResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Points"] && data["Points"].constructor === Array) {
-                this.points = [];
-                for (let item of data["Points"])
-                    this.points.push(Point.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): PointsAggregateResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new PointsAggregateResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.points && this.points.constructor === Array) {
-            data["Points"] = [];
-            for (let item of this.points)
-                data["Points"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPointsAggregateResult extends IAggregateResult {
-    points?: Point[] | undefined;
-}
-
-export class Point implements IPoint {
-    x?: number | undefined;
-    y?: number | undefined;
-
-    constructor(data?: IPoint) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.x = data["X"];
-            this.y = data["Y"];
-        }
-    }
-
-    static fromJS(data: any): Point {
-        data = typeof data === 'object' ? data : {};
-        let result = new Point();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["X"] = this.x;
-        data["Y"] = this.y;
-        return data;
-    }
-}
-
-export interface IPoint {
-    x?: number | undefined;
-    y?: number | undefined;
-}
-
-export class SumEstimationAggregateResult extends DoubleValueAggregateResult implements ISumEstimationAggregateResult {
-    sum?: number | undefined;
-    sumEstimation?: number | undefined;
-
-    constructor(data?: ISumEstimationAggregateResult) {
-        super(data);
-        this._discriminator = "SumEstimationAggregateResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.sum = data["Sum"];
-            this.sumEstimation = data["SumEstimation"];
-        }
-    }
-
-    static fromJS(data: any): SumEstimationAggregateResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new SumEstimationAggregateResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Sum"] = this.sum;
-        data["SumEstimation"] = this.sumEstimation;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISumEstimationAggregateResult extends IDoubleValueAggregateResult {
-    sum?: number | undefined;
-    sumEstimation?: number | undefined;
-}
-
-export class Brush implements IBrush {
-    brushIndex?: number | undefined;
-    brushEnum?: BrushEnum | undefined;
-
-    constructor(data?: IBrush) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.brushIndex = data["BrushIndex"];
-            this.brushEnum = data["BrushEnum"];
-        }
-    }
-
-    static fromJS(data: any): Brush {
-        data = typeof data === 'object' ? data : {};
-        let result = new Brush();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["BrushIndex"] = this.brushIndex;
-        data["BrushEnum"] = this.brushEnum;
-        return data;
-    }
-}
-
-export interface IBrush {
-    brushIndex?: number | undefined;
-    brushEnum?: BrushEnum | undefined;
-}
-
-export enum BrushEnum {
-    Overlap = 0,
-    Rest = 1,
-    All = 2,
-    UserSpecified = 3,
-}
-
-export abstract class BinRange implements IBinRange {
-    minValue?: number | undefined;
-    maxValue?: number | undefined;
-    targetBinNumber?: number | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IBinRange) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "BinRange";
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.minValue = data["MinValue"];
-            this.maxValue = data["MaxValue"];
-            this.targetBinNumber = data["TargetBinNumber"];
-        }
-    }
-
-    static fromJS(data: any): BinRange {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "NominalBinRange") {
-            let result = new NominalBinRange();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "QuantitativeBinRange") {
-            let result = new QuantitativeBinRange();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AggregateBinRange") {
-            let result = new AggregateBinRange();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AlphabeticBinRange") {
-            let result = new AlphabeticBinRange();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DateTimeBinRange") {
-            let result = new DateTimeBinRange();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'BinRange' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["MinValue"] = this.minValue;
-        data["MaxValue"] = this.maxValue;
-        data["TargetBinNumber"] = this.targetBinNumber;
-        return data;
-    }
-}
-
-export interface IBinRange {
-    minValue?: number | undefined;
-    maxValue?: number | undefined;
-    targetBinNumber?: number | undefined;
-}
-
-export class NominalBinRange extends BinRange implements INominalBinRange {
-    labelsValue?: { [key: string]: number; } | undefined;
-    valuesLabel?: { [key: string]: string; } | undefined;
-
-    constructor(data?: INominalBinRange) {
-        super(data);
-        this._discriminator = "NominalBinRange";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["LabelsValue"]) {
-                this.labelsValue = {};
-                for (let key in data["LabelsValue"]) {
-                    if (data["LabelsValue"].hasOwnProperty(key))
-                        this.labelsValue[key] = data["LabelsValue"][key];
-                }
-            }
-            if (data["ValuesLabel"]) {
-                this.valuesLabel = {};
-                for (let key in data["ValuesLabel"]) {
-                    if (data["ValuesLabel"].hasOwnProperty(key))
-                        this.valuesLabel[key] = data["ValuesLabel"][key];
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): NominalBinRange {
-        data = typeof data === 'object' ? data : {};
-        let result = new NominalBinRange();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.labelsValue) {
-            data["LabelsValue"] = {};
-            for (let key in this.labelsValue) {
-                if (this.labelsValue.hasOwnProperty(key))
-                    data["LabelsValue"][key] = this.labelsValue[key];
-            }
-        }
-        if (this.valuesLabel) {
-            data["ValuesLabel"] = {};
-            for (let key in this.valuesLabel) {
-                if (this.valuesLabel.hasOwnProperty(key))
-                    data["ValuesLabel"][key] = this.valuesLabel[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface INominalBinRange extends IBinRange {
-    labelsValue?: { [key: string]: number; } | undefined;
-    valuesLabel?: { [key: string]: string; } | undefined;
-}
-
-export class QuantitativeBinRange extends BinRange implements IQuantitativeBinRange {
-    isIntegerRange?: boolean | undefined;
-    step?: number | undefined;
-
-    constructor(data?: IQuantitativeBinRange) {
-        super(data);
-        this._discriminator = "QuantitativeBinRange";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.isIntegerRange = data["IsIntegerRange"];
-            this.step = data["Step"];
-        }
-    }
-
-    static fromJS(data: any): QuantitativeBinRange {
-        data = typeof data === 'object' ? data : {};
-        let result = new QuantitativeBinRange();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["IsIntegerRange"] = this.isIntegerRange;
-        data["Step"] = this.step;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IQuantitativeBinRange extends IBinRange {
-    isIntegerRange?: boolean | undefined;
-    step?: number | undefined;
-}
-
-export class AggregateBinRange extends BinRange implements IAggregateBinRange {
-
-    constructor(data?: IAggregateBinRange) {
-        super(data);
-        this._discriminator = "AggregateBinRange";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): AggregateBinRange {
-        data = typeof data === 'object' ? data : {};
-        let result = new AggregateBinRange();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAggregateBinRange extends IBinRange {
-}
-
-export class AlphabeticBinRange extends BinRange implements IAlphabeticBinRange {
-    prefix?: string | undefined;
-    labelsValue?: { [key: string]: number; } | undefined;
-    valuesLabel?: { [key: string]: string; } | undefined;
-
-    constructor(data?: IAlphabeticBinRange) {
-        super(data);
-        this._discriminator = "AlphabeticBinRange";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.prefix = data["Prefix"];
-            if (data["LabelsValue"]) {
-                this.labelsValue = {};
-                for (let key in data["LabelsValue"]) {
-                    if (data["LabelsValue"].hasOwnProperty(key))
-                        this.labelsValue[key] = data["LabelsValue"][key];
-                }
-            }
-            if (data["ValuesLabel"]) {
-                this.valuesLabel = {};
-                for (let key in data["ValuesLabel"]) {
-                    if (data["ValuesLabel"].hasOwnProperty(key))
-                        this.valuesLabel[key] = data["ValuesLabel"][key];
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): AlphabeticBinRange {
-        data = typeof data === 'object' ? data : {};
-        let result = new AlphabeticBinRange();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Prefix"] = this.prefix;
-        if (this.labelsValue) {
-            data["LabelsValue"] = {};
-            for (let key in this.labelsValue) {
-                if (this.labelsValue.hasOwnProperty(key))
-                    data["LabelsValue"][key] = this.labelsValue[key];
-            }
-        }
-        if (this.valuesLabel) {
-            data["ValuesLabel"] = {};
-            for (let key in this.valuesLabel) {
-                if (this.valuesLabel.hasOwnProperty(key))
-                    data["ValuesLabel"][key] = this.valuesLabel[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAlphabeticBinRange extends IBinRange {
-    prefix?: string | undefined;
-    labelsValue?: { [key: string]: number; } | undefined;
-    valuesLabel?: { [key: string]: string; } | undefined;
-}
-
-export class DateTimeBinRange extends BinRange implements IDateTimeBinRange {
-    step?: DateTimeStep | undefined;
-
-    constructor(data?: IDateTimeBinRange) {
-        super(data);
-        this._discriminator = "DateTimeBinRange";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.step = data["Step"] ? DateTimeStep.fromJS(data["Step"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): DateTimeBinRange {
-        data = typeof data === 'object' ? data : {};
-        let result = new DateTimeBinRange();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Step"] = this.step ? this.step.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDateTimeBinRange extends IBinRange {
-    step?: DateTimeStep | undefined;
-}
-
-export class DateTimeStep implements IDateTimeStep {
-    dateTimeStepGranularity?: DateTimeStepGranularity | undefined;
-    dateTimeStepValue?: number | undefined;
-    dateTimeStepMaxValue?: number | undefined;
-
-    constructor(data?: IDateTimeStep) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.dateTimeStepGranularity = data["DateTimeStepGranularity"];
-            this.dateTimeStepValue = data["DateTimeStepValue"];
-            this.dateTimeStepMaxValue = data["DateTimeStepMaxValue"];
-        }
-    }
-
-    static fromJS(data: any): DateTimeStep {
-        data = typeof data === 'object' ? data : {};
-        let result = new DateTimeStep();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["DateTimeStepGranularity"] = this.dateTimeStepGranularity;
-        data["DateTimeStepValue"] = this.dateTimeStepValue;
-        data["DateTimeStepMaxValue"] = this.dateTimeStepMaxValue;
-        return data;
-    }
-}
-
-export interface IDateTimeStep {
-    dateTimeStepGranularity?: DateTimeStepGranularity | undefined;
-    dateTimeStepValue?: number | undefined;
-    dateTimeStepMaxValue?: number | undefined;
-}
-
-export enum DateTimeStepGranularity {
-    Second = 0,
-    Minute = 1,
-    Hour = 2,
-    Day = 3,
-    Month = 4,
-    Year = 5,
-}
-
-export class Bin implements IBin {
-    aggregateResults?: AggregateResult[] | undefined;
-    count?: number | undefined;
-    binIndex?: BinIndex | undefined;
-    spans?: Span[] | undefined;
-    xSize?: number | undefined;
-    ySize?: number | undefined;
-
-    constructor(data?: IBin) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["AggregateResults"] && data["AggregateResults"].constructor === Array) {
-                this.aggregateResults = [];
-                for (let item of data["AggregateResults"]) {
-                    let fromJs = AggregateResult.fromJS(item);
-                    if (fromJs)
-                        this.aggregateResults.push(fromJs);
-                }
-            }
-            this.count = data["Count"];
-            this.binIndex = data["BinIndex"] ? BinIndex.fromJS(data["BinIndex"]) : <any>undefined;
-            if (data["Spans"] && data["Spans"].constructor === Array) {
-                this.spans = [];
-                for (let item of data["Spans"])
-                    this.spans.push(Span.fromJS(item));
-            }
-            this.xSize = data["XSize"];
-            this.ySize = data["YSize"];
-        }
-    }
-
-    static fromJS(data: any): Bin {
-        data = typeof data === 'object' ? data : {};
-        let result = new Bin();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.aggregateResults && this.aggregateResults.constructor === Array) {
-            data["AggregateResults"] = [];
-            for (let item of this.aggregateResults)
-                data["AggregateResults"].push(item.toJSON());
-        }
-        data["Count"] = this.count;
-        data["BinIndex"] = this.binIndex ? this.binIndex.toJSON() : <any>undefined;
-        if (this.spans && this.spans.constructor === Array) {
-            data["Spans"] = [];
-            for (let item of this.spans)
-                data["Spans"].push(item.toJSON());
-        }
-        data["XSize"] = this.xSize;
-        data["YSize"] = this.ySize;
-        return data;
-    }
-}
-
-export interface IBin {
-    aggregateResults?: AggregateResult[] | undefined;
-    count?: number | undefined;
-    binIndex?: BinIndex | undefined;
-    spans?: Span[] | undefined;
-    xSize?: number | undefined;
-    ySize?: number | undefined;
-}
-
-export class BinIndex implements IBinIndex {
-    indices?: number[] | undefined;
-    flatIndex?: number | undefined;
-
-    constructor(data?: IBinIndex) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["Indices"] && data["Indices"].constructor === Array) {
-                this.indices = [];
-                for (let item of data["Indices"])
-                    this.indices.push(item);
-            }
-            this.flatIndex = data["FlatIndex"];
-        }
-    }
-
-    static fromJS(data: any): BinIndex {
-        data = typeof data === 'object' ? data : {};
-        let result = new BinIndex();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.indices && this.indices.constructor === Array) {
-            data["Indices"] = [];
-            for (let item of this.indices)
-                data["Indices"].push(item);
-        }
-        data["FlatIndex"] = this.flatIndex;
-        return data;
-    }
-}
-
-export interface IBinIndex {
-    indices?: number[] | undefined;
-    flatIndex?: number | undefined;
-}
-
-export class Span implements ISpan {
-    min?: number | undefined;
-    max?: number | undefined;
-    index?: number | undefined;
-
-    constructor(data?: ISpan) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.min = data["Min"];
-            this.max = data["Max"];
-            this.index = data["Index"];
-        }
-    }
-
-    static fromJS(data: any): Span {
-        data = typeof data === 'object' ? data : {};
-        let result = new Span();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Min"] = this.min;
-        data["Max"] = this.max;
-        data["Index"] = this.index;
-        return data;
-    }
-}
-
-export interface ISpan {
-    min?: number | undefined;
-    max?: number | undefined;
-    index?: number | undefined;
-}
-
-export class ModelWealthResult extends Result implements IModelWealthResult {
-    wealth?: number | undefined;
-    startWealth?: number | undefined;
-
-    constructor(data?: IModelWealthResult) {
-        super(data);
-        this._discriminator = "ModelWealthResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.wealth = data["Wealth"];
-            this.startWealth = data["StartWealth"];
-        }
-    }
-
-    static fromJS(data: any): ModelWealthResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new ModelWealthResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Wealth"] = this.wealth;
-        data["StartWealth"] = this.startWealth;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IModelWealthResult extends IResult {
-    wealth?: number | undefined;
-    startWealth?: number | undefined;
-}
-
-export abstract class HypothesisTestResult extends Result implements IHypothesisTestResult {
-    pValue?: number | undefined;
-    statistic?: number | undefined;
-    support?: number | undefined;
-    sampleSizes?: number[] | undefined;
-    errorMessage?: string | undefined;
-
-    constructor(data?: IHypothesisTestResult) {
-        super(data);
-        this._discriminator = "HypothesisTestResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.pValue = data["PValue"];
-            this.statistic = data["Statistic"];
-            this.support = data["Support"];
-            if (data["SampleSizes"] && data["SampleSizes"].constructor === Array) {
-                this.sampleSizes = [];
-                for (let item of data["SampleSizes"])
-                    this.sampleSizes.push(item);
-            }
-            this.errorMessage = data["ErrorMessage"];
-        }
-    }
-
-    static fromJS(data: any): HypothesisTestResult {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "ChiSquaredTestResult") {
-            let result = new ChiSquaredTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CorrelationTestResult") {
-            let result = new CorrelationTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "KSTestResult") {
-            let result = new KSTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "RootMeanSquareTestResult") {
-            let result = new RootMeanSquareTestResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "TTestResult") {
-            let result = new TTestResult();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'HypothesisTestResult' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["PValue"] = this.pValue;
-        data["Statistic"] = this.statistic;
-        data["Support"] = this.support;
-        if (this.sampleSizes && this.sampleSizes.constructor === Array) {
-            data["SampleSizes"] = [];
-            for (let item of this.sampleSizes)
-                data["SampleSizes"].push(item);
-        }
-        data["ErrorMessage"] = this.errorMessage;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IHypothesisTestResult extends IResult {
-    pValue?: number | undefined;
-    statistic?: number | undefined;
-    support?: number | undefined;
-    sampleSizes?: number[] | undefined;
-    errorMessage?: string | undefined;
-}
-
-export abstract class ModelOperationResult extends Result implements IModelOperationResult {
-    modelId?: ModelId | undefined;
-
-    constructor(data?: IModelOperationResult) {
-        super(data);
-        this._discriminator = "ModelOperationResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): ModelOperationResult {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "NewModelOperationResult") {
-            let result = new NewModelOperationResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "AddComparisonResult") {
-            let result = new AddComparisonResult();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "GetModelStateResult") {
-            let result = new GetModelStateResult();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'ModelOperationResult' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IModelOperationResult extends IResult {
-    modelId?: ModelId | undefined;
-}
-
-export class RecommenderResult extends Result implements IRecommenderResult {
-    recommendedHistograms?: RecommendedHistogram[] | undefined;
-    totalCount?: number | undefined;
-
-    constructor(data?: IRecommenderResult) {
-        super(data);
-        this._discriminator = "RecommenderResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["RecommendedHistograms"] && data["RecommendedHistograms"].constructor === Array) {
-                this.recommendedHistograms = [];
-                for (let item of data["RecommendedHistograms"])
-                    this.recommendedHistograms.push(RecommendedHistogram.fromJS(item));
-            }
-            this.totalCount = data["TotalCount"];
-        }
-    }
-
-    static fromJS(data: any): RecommenderResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new RecommenderResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.recommendedHistograms && this.recommendedHistograms.constructor === Array) {
-            data["RecommendedHistograms"] = [];
-            for (let item of this.recommendedHistograms)
-                data["RecommendedHistograms"].push(item.toJSON());
-        }
-        data["TotalCount"] = this.totalCount;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRecommenderResult extends IResult {
-    recommendedHistograms?: RecommendedHistogram[] | undefined;
-    totalCount?: number | undefined;
-}
-
-export class RecommendedHistogram implements IRecommendedHistogram {
-    histogramResult?: HistogramResult | undefined;
-    selectedBinIndices?: BinIndex[] | undefined;
-    selections?: Selection[] | undefined;
-    pValue?: number | undefined;
-    significance?: boolean | undefined;
-    decision?: Decision | undefined;
-    effectSize?: number | undefined;
-    hypothesisTestResult?: HypothesisTestResult | undefined;
-    id?: string | undefined;
-    xAttribute?: Attribute | undefined;
-    yAttribute?: Attribute | undefined;
-
-    constructor(data?: IRecommendedHistogram) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.histogramResult = data["HistogramResult"] ? HistogramResult.fromJS(data["HistogramResult"]) : <any>undefined;
-            if (data["SelectedBinIndices"] && data["SelectedBinIndices"].constructor === Array) {
-                this.selectedBinIndices = [];
-                for (let item of data["SelectedBinIndices"])
-                    this.selectedBinIndices.push(BinIndex.fromJS(item));
-            }
-            if (data["Selections"] && data["Selections"].constructor === Array) {
-                this.selections = [];
-                for (let item of data["Selections"])
-                    this.selections.push(Selection.fromJS(item));
-            }
-            this.pValue = data["PValue"];
-            this.significance = data["Significance"];
-            this.decision = data["Decision"] ? Decision.fromJS(data["Decision"]) : <any>undefined;
-            this.effectSize = data["EffectSize"];
-            this.hypothesisTestResult = data["HypothesisTestResult"] ? HypothesisTestResult.fromJS(data["HypothesisTestResult"]) : <any>undefined;
-            this.id = data["Id"];
-            this.xAttribute = data["XAttribute"] ? Attribute.fromJS(data["XAttribute"]) : <any>undefined;
-            this.yAttribute = data["YAttribute"] ? Attribute.fromJS(data["YAttribute"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): RecommendedHistogram {
-        data = typeof data === 'object' ? data : {};
-        let result = new RecommendedHistogram();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["HistogramResult"] = this.histogramResult ? this.histogramResult.toJSON() : <any>undefined;
-        if (this.selectedBinIndices && this.selectedBinIndices.constructor === Array) {
-            data["SelectedBinIndices"] = [];
-            for (let item of this.selectedBinIndices)
-                data["SelectedBinIndices"].push(item.toJSON());
-        }
-        if (this.selections && this.selections.constructor === Array) {
-            data["Selections"] = [];
-            for (let item of this.selections)
-                data["Selections"].push(item.toJSON());
-        }
-        data["PValue"] = this.pValue;
-        data["Significance"] = this.significance;
-        data["Decision"] = this.decision ? this.decision.toJSON() : <any>undefined;
-        data["EffectSize"] = this.effectSize;
-        data["HypothesisTestResult"] = this.hypothesisTestResult ? this.hypothesisTestResult.toJSON() : <any>undefined;
-        data["Id"] = this.id;
-        data["XAttribute"] = this.xAttribute ? this.xAttribute.toJSON() : <any>undefined;
-        data["YAttribute"] = this.yAttribute ? this.yAttribute.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface IRecommendedHistogram {
-    histogramResult?: HistogramResult | undefined;
-    selectedBinIndices?: BinIndex[] | undefined;
-    selections?: Selection[] | undefined;
-    pValue?: number | undefined;
-    significance?: boolean | undefined;
-    decision?: Decision | undefined;
-    effectSize?: number | undefined;
-    hypothesisTestResult?: HypothesisTestResult | undefined;
-    id?: string | undefined;
-    xAttribute?: Attribute | undefined;
-    yAttribute?: Attribute | undefined;
-}
-
-export class Selection implements ISelection {
-    statements?: Statement[] | undefined;
-    filterHistogramOperationReference?: IOperationReference | undefined;
-
-    constructor(data?: ISelection) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["Statements"] && data["Statements"].constructor === Array) {
-                this.statements = [];
-                for (let item of data["Statements"])
-                    this.statements.push(Statement.fromJS(item));
-            }
-            this.filterHistogramOperationReference = data["FilterHistogramOperationReference"] ? IOperationReference.fromJS(data["FilterHistogramOperationReference"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): Selection {
-        data = typeof data === 'object' ? data : {};
-        let result = new Selection();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.statements && this.statements.constructor === Array) {
-            data["Statements"] = [];
-            for (let item of this.statements)
-                data["Statements"].push(item.toJSON());
-        }
-        data["FilterHistogramOperationReference"] = this.filterHistogramOperationReference ? this.filterHistogramOperationReference.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface ISelection {
-    statements?: Statement[] | undefined;
-    filterHistogramOperationReference?: IOperationReference | undefined;
-}
-
-export class Statement implements IStatement {
-    attribute?: Attribute | undefined;
-    predicate?: Predicate | undefined;
-    value?: any | undefined;
-
-    constructor(data?: IStatement) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.attribute = data["Attribute"] ? Attribute.fromJS(data["Attribute"]) : <any>undefined;
-            this.predicate = data["Predicate"];
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): Statement {
-        data = typeof data === 'object' ? data : {};
-        let result = new Statement();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Attribute"] = this.attribute ? this.attribute.toJSON() : <any>undefined;
-        data["Predicate"] = this.predicate;
-        data["Value"] = this.value;
-        return data;
-    }
-}
-
-export interface IStatement {
-    attribute?: Attribute | undefined;
-    predicate?: Predicate | undefined;
-    value?: any | undefined;
-}
-
-export enum Predicate {
-    EQUALS = 0,
-    LIKE = 1,
-    GREATER_THAN = 2,
-    LESS_THAN = 3,
-    GREATER_THAN_EQUAL = 4,
-    LESS_THAN_EQUAL = 5,
-    STARTS_WITH = 6,
-    ENDS_WITH = 7,
-    CONTAINS = 8,
-}
-
-export abstract class IOperationReference implements IIOperationReference {
-    id?: string | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IIOperationReference) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "IOperationReference";
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.id = data["Id"];
-        }
-    }
-
-    static fromJS(data: any): IOperationReference {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "OperationReference") {
-            let result = new OperationReference();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'IOperationReference' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["Id"] = this.id;
-        return data;
-    }
-}
-
-export interface IIOperationReference {
-    id?: string | undefined;
-}
-
-export class OperationReference extends IOperationReference implements IOperationReference {
-    id?: string | undefined;
-
-    constructor(data?: IOperationReference) {
-        super(data);
-        this._discriminator = "OperationReference";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.id = data["Id"];
-        }
-    }
-
-    static fromJS(data: any): OperationReference {
-        data = typeof data === 'object' ? data : {};
-        let result = new OperationReference();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IOperationReference extends IIOperationReference {
-    id?: string | undefined;
-}
-
-export class Decision extends Result implements IDecision {
-    comparisonId?: ComparisonId | undefined;
-    riskControlType?: RiskControlType | undefined;
-    significance?: boolean | undefined;
-    pValue?: number | undefined;
-    lhs?: number | undefined;
-    significanceLevel?: number | undefined;
-    sampleSizeEstimate?: number | undefined;
-
-    constructor(data?: IDecision) {
-        super(data);
-        this._discriminator = "Decision";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.comparisonId = data["ComparisonId"] ? ComparisonId.fromJS(data["ComparisonId"]) : <any>undefined;
-            this.riskControlType = data["RiskControlType"];
-            this.significance = data["Significance"];
-            this.pValue = data["PValue"];
-            this.lhs = data["Lhs"];
-            this.significanceLevel = data["SignificanceLevel"];
-            this.sampleSizeEstimate = data["SampleSizeEstimate"];
-        }
-    }
-
-    static fromJS(data: any): Decision {
-        data = typeof data === 'object' ? data : {};
-        let result = new Decision();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["ComparisonId"] = this.comparisonId ? this.comparisonId.toJSON() : <any>undefined;
-        data["RiskControlType"] = this.riskControlType;
-        data["Significance"] = this.significance;
-        data["PValue"] = this.pValue;
-        data["Lhs"] = this.lhs;
-        data["SignificanceLevel"] = this.significanceLevel;
-        data["SampleSizeEstimate"] = this.sampleSizeEstimate;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDecision extends IResult {
-    comparisonId?: ComparisonId | undefined;
-    riskControlType?: RiskControlType | undefined;
-    significance?: boolean | undefined;
-    pValue?: number | undefined;
-    lhs?: number | undefined;
-    significanceLevel?: number | undefined;
-    sampleSizeEstimate?: number | undefined;
-}
-
-export class ComparisonId implements IComparisonId {
-    value?: string | undefined;
-
-    constructor(data?: IComparisonId) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): ComparisonId {
-        data = typeof data === 'object' ? data : {};
-        let result = new ComparisonId();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        return data;
-    }
-}
-
-export interface IComparisonId {
-    value?: string | undefined;
-}
-
-export class OptimizerResult extends Result implements IOptimizerResult {
-    topKSolutions?: Solution[] | undefined;
-
-    constructor(data?: IOptimizerResult) {
-        super(data);
-        this._discriminator = "OptimizerResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["TopKSolutions"] && data["TopKSolutions"].constructor === Array) {
-                this.topKSolutions = [];
-                for (let item of data["TopKSolutions"])
-                    this.topKSolutions.push(Solution.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): OptimizerResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new OptimizerResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.topKSolutions && this.topKSolutions.constructor === Array) {
-            data["TopKSolutions"] = [];
-            for (let item of this.topKSolutions)
-                data["TopKSolutions"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IOptimizerResult extends IResult {
-    topKSolutions?: Solution[] | undefined;
-}
-
-export class Solution implements ISolution {
-    solutionId?: string | undefined;
-    stepDescriptions?: StepDescription[] | undefined;
-    pipelineDescription?: PipelineDescription | undefined;
-    score?: Score | undefined;
-    naiveScore?: Score | undefined;
-
-    constructor(data?: ISolution) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.solutionId = data["SolutionId"];
-            if (data["StepDescriptions"] && data["StepDescriptions"].constructor === Array) {
-                this.stepDescriptions = [];
-                for (let item of data["StepDescriptions"])
-                    this.stepDescriptions.push(StepDescription.fromJS(item));
-            }
-            this.pipelineDescription = data["PipelineDescription"] ? PipelineDescription.fromJS(data["PipelineDescription"]) : <any>undefined;
-            this.score = data["Score"] ? Score.fromJS(data["Score"]) : <any>undefined;
-            this.naiveScore = data["NaiveScore"] ? Score.fromJS(data["NaiveScore"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): Solution {
-        data = typeof data === 'object' ? data : {};
-        let result = new Solution();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SolutionId"] = this.solutionId;
-        if (this.stepDescriptions && this.stepDescriptions.constructor === Array) {
-            data["StepDescriptions"] = [];
-            for (let item of this.stepDescriptions)
-                data["StepDescriptions"].push(item.toJSON());
-        }
-        data["PipelineDescription"] = this.pipelineDescription ? this.pipelineDescription.toJSON() : <any>undefined;
-        data["Score"] = this.score ? this.score.toJSON() : <any>undefined;
-        data["NaiveScore"] = this.naiveScore ? this.naiveScore.toJSON() : <any>undefined;
-        return data;
-    }
-}
-
-export interface ISolution {
-    solutionId?: string | undefined;
-    stepDescriptions?: StepDescription[] | undefined;
-    pipelineDescription?: PipelineDescription | undefined;
-    score?: Score | undefined;
-    naiveScore?: Score | undefined;
-}
-
-export class StepDescription implements IStepDescription {
-
-    protected _discriminator: string;
-
-    constructor(data?: IStepDescription) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "StepDescription";
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): StepDescription {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "SubpipelineStepDescription") {
-            let result = new SubpipelineStepDescription();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PrimitiveStepDescription") {
-            let result = new PrimitiveStepDescription();
-            result.init(data);
-            return result;
-        }
-        let result = new StepDescription();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        return data;
-    }
-}
-
-export interface IStepDescription {
-}
-
-export class SubpipelineStepDescription extends StepDescription implements ISubpipelineStepDescription {
-    steps?: StepDescription[] | undefined;
-
-    constructor(data?: ISubpipelineStepDescription) {
-        super(data);
-        this._discriminator = "SubpipelineStepDescription";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Steps"] && data["Steps"].constructor === Array) {
-                this.steps = [];
-                for (let item of data["Steps"])
-                    this.steps.push(StepDescription.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): SubpipelineStepDescription {
-        data = typeof data === 'object' ? data : {};
-        let result = new SubpipelineStepDescription();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.steps && this.steps.constructor === Array) {
-            data["Steps"] = [];
-            for (let item of this.steps)
-                data["Steps"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISubpipelineStepDescription extends IStepDescription {
-    steps?: StepDescription[] | undefined;
-}
-
-export class PipelineDescription implements IPipelineDescription {
-    id?: string | undefined;
-    name?: string | undefined;
-    description?: string | undefined;
-    inputs?: PipelineDescriptionInput[] | undefined;
-    outputs?: PipelineDescriptionOutput[] | undefined;
-    steps?: PipelineDescriptionStep[] | undefined;
-
-    constructor(data?: IPipelineDescription) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.id = data["Id"];
-            this.name = data["Name"];
-            this.description = data["Description"];
-            if (data["Inputs"] && data["Inputs"].constructor === Array) {
-                this.inputs = [];
-                for (let item of data["Inputs"])
-                    this.inputs.push(PipelineDescriptionInput.fromJS(item));
-            }
-            if (data["Outputs"] && data["Outputs"].constructor === Array) {
-                this.outputs = [];
-                for (let item of data["Outputs"])
-                    this.outputs.push(PipelineDescriptionOutput.fromJS(item));
-            }
-            if (data["Steps"] && data["Steps"].constructor === Array) {
-                this.steps = [];
-                for (let item of data["Steps"])
-                    this.steps.push(PipelineDescriptionStep.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): PipelineDescription {
-        data = typeof data === 'object' ? data : {};
-        let result = new PipelineDescription();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        data["Name"] = this.name;
-        data["Description"] = this.description;
-        if (this.inputs && this.inputs.constructor === Array) {
-            data["Inputs"] = [];
-            for (let item of this.inputs)
-                data["Inputs"].push(item.toJSON());
-        }
-        if (this.outputs && this.outputs.constructor === Array) {
-            data["Outputs"] = [];
-            for (let item of this.outputs)
-                data["Outputs"].push(item.toJSON());
-        }
-        if (this.steps && this.steps.constructor === Array) {
-            data["Steps"] = [];
-            for (let item of this.steps)
-                data["Steps"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IPipelineDescription {
-    id?: string | undefined;
-    name?: string | undefined;
-    description?: string | undefined;
-    inputs?: PipelineDescriptionInput[] | undefined;
-    outputs?: PipelineDescriptionOutput[] | undefined;
-    steps?: PipelineDescriptionStep[] | undefined;
-}
-
-export class PipelineDescriptionInput implements IPipelineDescriptionInput {
-    name?: string | undefined;
-
-    constructor(data?: IPipelineDescriptionInput) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.name = data["Name"];
-        }
-    }
-
-    static fromJS(data: any): PipelineDescriptionInput {
-        data = typeof data === 'object' ? data : {};
-        let result = new PipelineDescriptionInput();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Name"] = this.name;
-        return data;
-    }
-}
-
-export interface IPipelineDescriptionInput {
-    name?: string | undefined;
-}
-
-export class PipelineDescriptionOutput implements IPipelineDescriptionOutput {
-    name?: string | undefined;
-    data?: string | undefined;
-
-    constructor(data?: IPipelineDescriptionOutput) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.name = data["Name"];
-            this.data = data["Data"];
-        }
-    }
-
-    static fromJS(data: any): PipelineDescriptionOutput {
-        data = typeof data === 'object' ? data : {};
-        let result = new PipelineDescriptionOutput();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Name"] = this.name;
-        data["Data"] = this.data;
-        return data;
-    }
-}
-
-export interface IPipelineDescriptionOutput {
-    name?: string | undefined;
-    data?: string | undefined;
-}
-
-export class PipelineDescriptionStep implements IPipelineDescriptionStep {
-    outputs?: StepOutput[] | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IPipelineDescriptionStep) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "PipelineDescriptionStep";
-    }
-
-    init(data?: any) {
-        if (data) {
-            if (data["Outputs"] && data["Outputs"].constructor === Array) {
-                this.outputs = [];
-                for (let item of data["Outputs"])
-                    this.outputs.push(StepOutput.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): PipelineDescriptionStep {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "PlaceholderPipelineDescriptionStep") {
-            let result = new PlaceholderPipelineDescriptionStep();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "SubpipelinePipelineDescriptionStep") {
-            let result = new SubpipelinePipelineDescriptionStep();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PrimitivePipelineDescriptionStep") {
-            let result = new PrimitivePipelineDescriptionStep();
-            result.init(data);
-            return result;
-        }
-        let result = new PipelineDescriptionStep();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        if (this.outputs && this.outputs.constructor === Array) {
-            data["Outputs"] = [];
-            for (let item of this.outputs)
-                data["Outputs"].push(item.toJSON());
-        }
-        return data;
-    }
-}
-
-export interface IPipelineDescriptionStep {
-    outputs?: StepOutput[] | undefined;
-}
-
-export class StepOutput implements IStepOutput {
-    id?: string | undefined;
-
-    constructor(data?: IStepOutput) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.id = data["Id"];
-        }
-    }
-
-    static fromJS(data: any): StepOutput {
-        data = typeof data === 'object' ? data : {};
-        let result = new StepOutput();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        return data;
-    }
-}
-
-export interface IStepOutput {
-    id?: string | undefined;
-}
-
-export class PlaceholderPipelineDescriptionStep extends PipelineDescriptionStep implements IPlaceholderPipelineDescriptionStep {
-    inputs?: StepInput[] | undefined;
-
-    constructor(data?: IPlaceholderPipelineDescriptionStep) {
-        super(data);
-        this._discriminator = "PlaceholderPipelineDescriptionStep";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Inputs"] && data["Inputs"].constructor === Array) {
-                this.inputs = [];
-                for (let item of data["Inputs"])
-                    this.inputs.push(StepInput.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): PlaceholderPipelineDescriptionStep {
-        data = typeof data === 'object' ? data : {};
-        let result = new PlaceholderPipelineDescriptionStep();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.inputs && this.inputs.constructor === Array) {
-            data["Inputs"] = [];
-            for (let item of this.inputs)
-                data["Inputs"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPlaceholderPipelineDescriptionStep extends IPipelineDescriptionStep {
-    inputs?: StepInput[] | undefined;
-}
-
-export class StepInput implements IStepInput {
-    data?: string | undefined;
-
-    constructor(data?: IStepInput) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.data = data["Data"];
-        }
-    }
-
-    static fromJS(data: any): StepInput {
-        data = typeof data === 'object' ? data : {};
-        let result = new StepInput();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Data"] = this.data;
-        return data;
-    }
-}
-
-export interface IStepInput {
-    data?: string | undefined;
-}
-
-export class SubpipelinePipelineDescriptionStep extends PipelineDescriptionStep implements ISubpipelinePipelineDescriptionStep {
-    pipelineDescription?: PipelineDescription | undefined;
-    inputs?: StepInput[] | undefined;
-
-    constructor(data?: ISubpipelinePipelineDescriptionStep) {
-        super(data);
-        this._discriminator = "SubpipelinePipelineDescriptionStep";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.pipelineDescription = data["PipelineDescription"] ? PipelineDescription.fromJS(data["PipelineDescription"]) : <any>undefined;
-            if (data["Inputs"] && data["Inputs"].constructor === Array) {
-                this.inputs = [];
-                for (let item of data["Inputs"])
-                    this.inputs.push(StepInput.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): SubpipelinePipelineDescriptionStep {
-        data = typeof data === 'object' ? data : {};
-        let result = new SubpipelinePipelineDescriptionStep();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["PipelineDescription"] = this.pipelineDescription ? this.pipelineDescription.toJSON() : <any>undefined;
-        if (this.inputs && this.inputs.constructor === Array) {
-            data["Inputs"] = [];
-            for (let item of this.inputs)
-                data["Inputs"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISubpipelinePipelineDescriptionStep extends IPipelineDescriptionStep {
-    pipelineDescription?: PipelineDescription | undefined;
-    inputs?: StepInput[] | undefined;
-}
-
-export class PrimitivePipelineDescriptionStep extends PipelineDescriptionStep implements IPrimitivePipelineDescriptionStep {
-    primitive?: Primitive | undefined;
-    arguments?: { [key: string]: PrimitiveStepArgument; } | undefined;
-    hyperparams?: { [key: string]: PrimitiveStepHyperparameter; } | undefined;
-
-    constructor(data?: IPrimitivePipelineDescriptionStep) {
-        super(data);
-        this._discriminator = "PrimitivePipelineDescriptionStep";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.primitive = data["Primitive"] ? Primitive.fromJS(data["Primitive"]) : <any>undefined;
-            if (data["Arguments"]) {
-                this.arguments = {};
-                for (let key in data["Arguments"]) {
-                    if (data["Arguments"].hasOwnProperty(key))
-                        this.arguments[key] = data["Arguments"][key] ? PrimitiveStepArgument.fromJS(data["Arguments"][key]) : new PrimitiveStepArgument();
-                }
-            }
-            if (data["Hyperparams"]) {
-                this.hyperparams = {};
-                for (let key in data["Hyperparams"]) {
-                    if (data["Hyperparams"].hasOwnProperty(key))
-                        this.hyperparams[key] = data["Hyperparams"][key] ? PrimitiveStepHyperparameter.fromJS(data["Hyperparams"][key]) : new PrimitiveStepHyperparameter();
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): PrimitivePipelineDescriptionStep {
-        data = typeof data === 'object' ? data : {};
-        let result = new PrimitivePipelineDescriptionStep();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Primitive"] = this.primitive ? this.primitive.toJSON() : <any>undefined;
-        if (this.arguments) {
-            data["Arguments"] = {};
-            for (let key in this.arguments) {
-                if (this.arguments.hasOwnProperty(key))
-                    data["Arguments"][key] = this.arguments[key];
-            }
-        }
-        if (this.hyperparams) {
-            data["Hyperparams"] = {};
-            for (let key in this.hyperparams) {
-                if (this.hyperparams.hasOwnProperty(key))
-                    data["Hyperparams"][key] = this.hyperparams[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPrimitivePipelineDescriptionStep extends IPipelineDescriptionStep {
-    primitive?: Primitive | undefined;
-    arguments?: { [key: string]: PrimitiveStepArgument; } | undefined;
-    hyperparams?: { [key: string]: PrimitiveStepHyperparameter; } | undefined;
-}
-
-export class Primitive implements IPrimitive {
-    id?: string | undefined;
-    version?: string | undefined;
-    pythonPath?: string | undefined;
-    name?: string | undefined;
-    digest?: string | undefined;
-
-    constructor(data?: IPrimitive) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.id = data["Id"];
-            this.version = data["Version"];
-            this.pythonPath = data["PythonPath"];
-            this.name = data["Name"];
-            this.digest = data["Digest"];
-        }
-    }
-
-    static fromJS(data: any): Primitive {
-        data = typeof data === 'object' ? data : {};
-        let result = new Primitive();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Id"] = this.id;
-        data["Version"] = this.version;
-        data["PythonPath"] = this.pythonPath;
-        data["Name"] = this.name;
-        data["Digest"] = this.digest;
-        return data;
-    }
-}
-
-export interface IPrimitive {
-    id?: string | undefined;
-    version?: string | undefined;
-    pythonPath?: string | undefined;
-    name?: string | undefined;
-    digest?: string | undefined;
-}
-
-export class PrimitiveStepHyperparameter implements IPrimitiveStepHyperparameter {
-
-    protected _discriminator: string;
-
-    constructor(data?: IPrimitiveStepHyperparameter) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "PrimitiveStepHyperparameter";
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): PrimitiveStepHyperparameter {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "PrimitiveStepArgument") {
-            let result = new PrimitiveStepArgument();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DataArguments") {
-            let result = new DataArguments();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PrimitiveArgument") {
-            let result = new PrimitiveArgument();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PrimitiveArguments") {
-            let result = new PrimitiveArguments();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "ValueArgument") {
-            let result = new ValueArgument();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "ContainerArgument") {
-            let result = new ContainerArgument();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DataArgument") {
-            let result = new DataArgument();
-            result.init(data);
-            return result;
-        }
-        let result = new PrimitiveStepHyperparameter();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        return data;
-    }
-}
-
-export interface IPrimitiveStepHyperparameter {
-}
-
-export class PrimitiveStepArgument extends PrimitiveStepHyperparameter implements IPrimitiveStepArgument {
-
-    protected _discriminator: string;
-
-    constructor(data?: IPrimitiveStepArgument) {
-        super(data);
-        this._discriminator = "PrimitiveStepArgument";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): PrimitiveStepArgument {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "ContainerArgument") {
-            let result = new ContainerArgument();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DataArgument") {
-            let result = new DataArgument();
-            result.init(data);
-            return result;
-        }
-        let result = new PrimitiveStepArgument();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPrimitiveStepArgument extends IPrimitiveStepHyperparameter {
-}
-
-export class DataArguments extends PrimitiveStepHyperparameter implements IDataArguments {
-    data?: string[] | undefined;
-
-    constructor(data?: IDataArguments) {
-        super(data);
-        this._discriminator = "DataArguments";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Data"] && data["Data"].constructor === Array) {
-                this.data = [];
-                for (let item of data["Data"])
-                    this.data.push(item);
-            }
-        }
-    }
-
-    static fromJS(data: any): DataArguments {
-        data = typeof data === 'object' ? data : {};
-        let result = new DataArguments();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.data && this.data.constructor === Array) {
-            data["Data"] = [];
-            for (let item of this.data)
-                data["Data"].push(item);
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDataArguments extends IPrimitiveStepHyperparameter {
-    data?: string[] | undefined;
-}
-
-export class PrimitiveArgument extends PrimitiveStepHyperparameter implements IPrimitiveArgument {
-    data?: number | undefined;
-
-    constructor(data?: IPrimitiveArgument) {
-        super(data);
-        this._discriminator = "PrimitiveArgument";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.data = data["Data"];
-        }
-    }
-
-    static fromJS(data: any): PrimitiveArgument {
-        data = typeof data === 'object' ? data : {};
-        let result = new PrimitiveArgument();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Data"] = this.data;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPrimitiveArgument extends IPrimitiveStepHyperparameter {
-    data?: number | undefined;
-}
-
-export class PrimitiveArguments extends PrimitiveStepHyperparameter implements IPrimitiveArguments {
-    data?: number[] | undefined;
-
-    constructor(data?: IPrimitiveArguments) {
-        super(data);
-        this._discriminator = "PrimitiveArguments";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Data"] && data["Data"].constructor === Array) {
-                this.data = [];
-                for (let item of data["Data"])
-                    this.data.push(item);
-            }
-        }
-    }
-
-    static fromJS(data: any): PrimitiveArguments {
-        data = typeof data === 'object' ? data : {};
-        let result = new PrimitiveArguments();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.data && this.data.constructor === Array) {
-            data["Data"] = [];
-            for (let item of this.data)
-                data["Data"].push(item);
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPrimitiveArguments extends IPrimitiveStepHyperparameter {
-    data?: number[] | undefined;
-}
-
-export class ValueArgument extends PrimitiveStepHyperparameter implements IValueArgument {
-    data?: Value | undefined;
-
-    constructor(data?: IValueArgument) {
-        super(data);
-        this._discriminator = "ValueArgument";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.data = data["Data"] ? Value.fromJS(data["Data"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): ValueArgument {
-        data = typeof data === 'object' ? data : {};
-        let result = new ValueArgument();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Data"] = this.data ? this.data.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IValueArgument extends IPrimitiveStepHyperparameter {
-    data?: Value | undefined;
-}
-
-export abstract class Value implements IValue {
-
-    protected _discriminator: string;
-
-    constructor(data?: IValue) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-        this._discriminator = "Value";
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): Value {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "ErrorValue") {
-            let result = new ErrorValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DoubleValue") {
-            let result = new DoubleValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "LongValue") {
-            let result = new LongValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "BoolValue") {
-            let result = new BoolValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "StringValue") {
-            let result = new StringValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "DatasetUriValue") {
-            let result = new DatasetUriValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "CsvUriValue") {
-            let result = new CsvUriValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PickleUriValue") {
-            let result = new PickleUriValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PickleBlobValue") {
-            let result = new PickleBlobValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "PlasmaIdValue") {
-            let result = new PlasmaIdValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "BytesValue") {
-            let result = new BytesValue();
-            result.init(data);
-            return result;
-        }
-        if (data["discriminator"] === "ListValue") {
-            let result = new ListValue();
-            result.init(data);
-            return result;
-        }
-        throw new Error("The abstract class 'Value' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        return data;
-    }
-}
-
-export interface IValue {
-}
-
-export class ErrorValue extends Value implements IErrorValue {
-    message?: string | undefined;
-
-    constructor(data?: IErrorValue) {
-        super(data);
-        this._discriminator = "ErrorValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.message = data["Message"];
-        }
-    }
-
-    static fromJS(data: any): ErrorValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new ErrorValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Message"] = this.message;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IErrorValue extends IValue {
-    message?: string | undefined;
-}
-
-export class DoubleValue extends Value implements IDoubleValue {
-    value?: number | undefined;
-
-    constructor(data?: IDoubleValue) {
-        super(data);
-        this._discriminator = "DoubleValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): DoubleValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new DoubleValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDoubleValue extends IValue {
-    value?: number | undefined;
-}
-
-export class LongValue extends Value implements ILongValue {
-    value?: number | undefined;
-
-    constructor(data?: ILongValue) {
-        super(data);
-        this._discriminator = "LongValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): LongValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new LongValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ILongValue extends IValue {
-    value?: number | undefined;
-}
-
-export class BoolValue extends Value implements IBoolValue {
-    value?: boolean | undefined;
-
-    constructor(data?: IBoolValue) {
-        super(data);
-        this._discriminator = "BoolValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): BoolValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new BoolValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IBoolValue extends IValue {
-    value?: boolean | undefined;
-}
-
-export class StringValue extends Value implements IStringValue {
-    value?: string | undefined;
-
-    constructor(data?: IStringValue) {
-        super(data);
-        this._discriminator = "StringValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): StringValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new StringValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IStringValue extends IValue {
-    value?: string | undefined;
-}
-
-export class DatasetUriValue extends Value implements IDatasetUriValue {
-    value?: string | undefined;
-
-    constructor(data?: IDatasetUriValue) {
-        super(data);
-        this._discriminator = "DatasetUriValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): DatasetUriValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new DatasetUriValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDatasetUriValue extends IValue {
-    value?: string | undefined;
-}
-
-export class CsvUriValue extends Value implements ICsvUriValue {
-    value?: string | undefined;
-
-    constructor(data?: ICsvUriValue) {
-        super(data);
-        this._discriminator = "CsvUriValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): CsvUriValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new CsvUriValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ICsvUriValue extends IValue {
-    value?: string | undefined;
-}
-
-export class PickleUriValue extends Value implements IPickleUriValue {
-    value?: string | undefined;
-
-    constructor(data?: IPickleUriValue) {
-        super(data);
-        this._discriminator = "PickleUriValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): PickleUriValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new PickleUriValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPickleUriValue extends IValue {
-    value?: string | undefined;
-}
-
-export class PickleBlobValue extends Value implements IPickleBlobValue {
-    value?: string | undefined;
-
-    constructor(data?: IPickleBlobValue) {
-        super(data);
-        this._discriminator = "PickleBlobValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): PickleBlobValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new PickleBlobValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPickleBlobValue extends IValue {
-    value?: string | undefined;
-}
-
-export class PlasmaIdValue extends Value implements IPlasmaIdValue {
-    value?: string | undefined;
-
-    constructor(data?: IPlasmaIdValue) {
-        super(data);
-        this._discriminator = "PlasmaIdValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): PlasmaIdValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new PlasmaIdValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPlasmaIdValue extends IValue {
-    value?: string | undefined;
-}
-
-export class BytesValue extends Value implements IBytesValue {
-    value?: string | undefined;
-
-    constructor(data?: IBytesValue) {
-        super(data);
-        this._discriminator = "BytesValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): BytesValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new BytesValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IBytesValue extends IValue {
-    value?: string | undefined;
-}
-
-export class ContainerArgument extends PrimitiveStepArgument implements IContainerArgument {
-    data?: string | undefined;
-
-    constructor(data?: IContainerArgument) {
-        super(data);
-        this._discriminator = "ContainerArgument";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.data = data["Data"];
-        }
-    }
-
-    static fromJS(data: any): ContainerArgument {
-        data = typeof data === 'object' ? data : {};
-        let result = new ContainerArgument();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Data"] = this.data;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IContainerArgument extends IPrimitiveStepArgument {
-    data?: string | undefined;
-}
-
-export class Score implements IScore {
-    metricType?: MetricType | undefined;
-    value?: number | undefined;
-
-    constructor(data?: IScore) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.metricType = data["MetricType"];
-            this.value = data["Value"];
-        }
-    }
-
-    static fromJS(data: any): Score {
-        data = typeof data === 'object' ? data : {};
-        let result = new Score();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["MetricType"] = this.metricType;
-        data["Value"] = this.value;
-        return data;
-    }
-}
-
-export interface IScore {
-    metricType?: MetricType | undefined;
-    value?: number | undefined;
-}
-
-export class ExampleResult extends Result implements IExampleResult {
-    resultValues?: { [key: string]: string; } | undefined;
-    message?: string | undefined;
-
-    constructor(data?: IExampleResult) {
-        super(data);
-        this._discriminator = "ExampleResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["ResultValues"]) {
-                this.resultValues = {};
-                for (let key in data["ResultValues"]) {
-                    if (data["ResultValues"].hasOwnProperty(key))
-                        this.resultValues[key] = data["ResultValues"][key];
-                }
-            }
-            this.message = data["Message"];
-        }
-    }
-
-    static fromJS(data: any): ExampleResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new ExampleResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.resultValues) {
-            data["ResultValues"] = {};
-            for (let key in this.resultValues) {
-                if (this.resultValues.hasOwnProperty(key))
-                    data["ResultValues"][key] = this.resultValues[key];
-            }
-        }
-        data["Message"] = this.message;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IExampleResult extends IResult {
-    resultValues?: { [key: string]: string; } | undefined;
-    message?: string | undefined;
-}
-
-export class NewModelOperationResult extends ModelOperationResult implements INewModelOperationResult {
-
-    constructor(data?: INewModelOperationResult) {
-        super(data);
-        this._discriminator = "NewModelOperationResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): NewModelOperationResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new NewModelOperationResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface INewModelOperationResult extends IModelOperationResult {
-}
-
-export class AddComparisonResult extends ModelOperationResult implements IAddComparisonResult {
-    comparisonId?: ComparisonId | undefined;
-
-    constructor(data?: IAddComparisonResult) {
-        super(data);
-        this._discriminator = "AddComparisonResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.comparisonId = data["ComparisonId"] ? ComparisonId.fromJS(data["ComparisonId"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): AddComparisonResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new AddComparisonResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["ComparisonId"] = this.comparisonId ? this.comparisonId.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAddComparisonResult extends IModelOperationResult {
-    comparisonId?: ComparisonId | undefined;
-}
-
-export class GetModelStateResult extends ModelOperationResult implements IGetModelStateResult {
-    decisions?: Decision[] | undefined;
-    startingWealth?: number | undefined;
-    currentWealth?: number | undefined;
-
-    constructor(data?: IGetModelStateResult) {
-        super(data);
-        this._discriminator = "GetModelStateResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Decisions"] && data["Decisions"].constructor === Array) {
-                this.decisions = [];
-                for (let item of data["Decisions"])
-                    this.decisions.push(Decision.fromJS(item));
-            }
-            this.startingWealth = data["StartingWealth"];
-            this.currentWealth = data["CurrentWealth"];
-        }
-    }
-
-    static fromJS(data: any): GetModelStateResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new GetModelStateResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.decisions && this.decisions.constructor === Array) {
-            data["Decisions"] = [];
-            for (let item of this.decisions)
-                data["Decisions"].push(item.toJSON());
-        }
-        data["StartingWealth"] = this.startingWealth;
-        data["CurrentWealth"] = this.currentWealth;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IGetModelStateResult extends IModelOperationResult {
-    decisions?: Decision[] | undefined;
-    startingWealth?: number | undefined;
-    currentWealth?: number | undefined;
-}
-
-export class AggregateKey implements IAggregateKey {
-    aggregateParameterIndex?: number | undefined;
-    brushIndex?: number | undefined;
-
-    constructor(data?: IAggregateKey) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.aggregateParameterIndex = data["AggregateParameterIndex"];
-            this.brushIndex = data["BrushIndex"];
-        }
-    }
-
-    static fromJS(data: any): AggregateKey {
-        data = typeof data === 'object' ? data : {};
-        let result = new AggregateKey();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["AggregateParameterIndex"] = this.aggregateParameterIndex;
-        data["BrushIndex"] = this.brushIndex;
-        return data;
-    }
-}
-
-export interface IAggregateKey {
-    aggregateParameterIndex?: number | undefined;
-    brushIndex?: number | undefined;
-}
-
-export abstract class IResult implements IIResult {
-
-    constructor(data?: IIResult) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): IResult {
-        data = typeof data === 'object' ? data : {};
-        throw new Error("The abstract class 'IResult' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        return data;
-    }
-}
-
-export interface IIResult {
-}
-
-export class DataArgument extends PrimitiveStepArgument implements IDataArgument {
-    data?: string | undefined;
-
-    constructor(data?: IDataArgument) {
-        super(data);
-        this._discriminator = "DataArgument";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.data = data["Data"];
-        }
-    }
-
-    static fromJS(data: any): DataArgument {
-        data = typeof data === 'object' ? data : {};
-        let result = new DataArgument();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Data"] = this.data;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IDataArgument extends IPrimitiveStepArgument {
-    data?: string | undefined;
-}
-
-export class ListValue extends Value implements IListValue {
-    items?: Value[] | undefined;
-
-    constructor(data?: IListValue) {
-        super(data);
-        this._discriminator = "ListValue";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Items"] && data["Items"].constructor === Array) {
-                this.items = [];
-                for (let item of data["Items"])
-                    this.items.push(Value.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): ListValue {
-        data = typeof data === 'object' ? data : {};
-        let result = new ListValue();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.items && this.items.constructor === Array) {
-            data["Items"] = [];
-            for (let item of this.items)
-                data["Items"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IListValue extends IValue {
-    items?: Value[] | undefined;
-}
-
-export class Metrics implements IMetrics {
-    averageAccuracy?: number | undefined;
-    averageRSquared?: number | undefined;
-    f1Macro?: number | undefined;
-
-    constructor(data?: IMetrics) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.averageAccuracy = data["AverageAccuracy"];
-            this.averageRSquared = data["AverageRSquared"];
-            this.f1Macro = data["F1Macro"];
-        }
-    }
-
-    static fromJS(data: any): Metrics {
-        data = typeof data === 'object' ? data : {};
-        let result = new Metrics();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["AverageAccuracy"] = this.averageAccuracy;
-        data["AverageRSquared"] = this.averageRSquared;
-        data["F1Macro"] = this.f1Macro;
-        return data;
-    }
-}
-
-export interface IMetrics {
-    averageAccuracy?: number | undefined;
-    averageRSquared?: number | undefined;
-    f1Macro?: number | undefined;
-}
-
-export class FeatureImportanceOperationParameters extends DistOperationParameters implements IFeatureImportanceOperationParameters {
-    solutionId?: string | undefined;
-
-    constructor(data?: IFeatureImportanceOperationParameters) {
-        super(data);
-        this._discriminator = "FeatureImportanceOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.solutionId = data["SolutionId"];
-        }
-    }
-
-    static fromJS(data: any): FeatureImportanceOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new FeatureImportanceOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SolutionId"] = this.solutionId;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IFeatureImportanceOperationParameters extends IDistOperationParameters {
-    solutionId?: string | undefined;
-}
-
-export class FeatureImportanceResult extends Result implements IFeatureImportanceResult {
-    featureImportances?: { [key: string]: TupleOfDoubleAndDouble; } | undefined;
-
-    constructor(data?: IFeatureImportanceResult) {
-        super(data);
-        this._discriminator = "FeatureImportanceResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["FeatureImportances"]) {
-                this.featureImportances = {};
-                for (let key in data["FeatureImportances"]) {
-                    if (data["FeatureImportances"].hasOwnProperty(key))
-                        this.featureImportances[key] = data["FeatureImportances"][key] ? TupleOfDoubleAndDouble.fromJS(data["FeatureImportances"][key]) : new TupleOfDoubleAndDouble();
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): FeatureImportanceResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new FeatureImportanceResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.featureImportances) {
-            data["FeatureImportances"] = {};
-            for (let key in this.featureImportances) {
-                if (this.featureImportances.hasOwnProperty(key))
-                    data["FeatureImportances"][key] = this.featureImportances[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IFeatureImportanceResult extends IResult {
-    featureImportances?: { [key: string]: TupleOfDoubleAndDouble; } | undefined;
-}
-
-export class TupleOfDoubleAndDouble implements ITupleOfDoubleAndDouble {
-    item1?: number | undefined;
-    item2?: number | undefined;
-
-    constructor(data?: ITupleOfDoubleAndDouble) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.item1 = data["Item1"];
-            this.item2 = data["Item2"];
-        }
-    }
-
-    static fromJS(data: any): TupleOfDoubleAndDouble {
-        data = typeof data === 'object' ? data : {};
-        let result = new TupleOfDoubleAndDouble();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Item1"] = this.item1;
-        data["Item2"] = this.item2;
-        return data;
-    }
-}
-
-export interface ITupleOfDoubleAndDouble {
-    item1?: number | undefined;
-    item2?: number | undefined;
-}
-
-export class PrimitiveStepDescription extends StepDescription implements IPrimitiveStepDescription {
-    hyperparams?: { [key: string]: Value; } | undefined;
-
-    constructor(data?: IPrimitiveStepDescription) {
-        super(data);
-        this._discriminator = "PrimitiveStepDescription";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Hyperparams"]) {
-                this.hyperparams = {};
-                for (let key in data["Hyperparams"]) {
-                    if (data["Hyperparams"].hasOwnProperty(key))
-                        this.hyperparams[key] = data["Hyperparams"][key] ? Value.fromJS(data["Hyperparams"][key]) : <any>undefined;
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): PrimitiveStepDescription {
-        data = typeof data === 'object' ? data : {};
-        let result = new PrimitiveStepDescription();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.hyperparams) {
-            data["Hyperparams"] = {};
-            for (let key in this.hyperparams) {
-                if (this.hyperparams.hasOwnProperty(key))
-                    data["Hyperparams"][key] = this.hyperparams[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IPrimitiveStepDescription extends IStepDescription {
-    hyperparams?: { [key: string]: Value; } | undefined;
-}
-
-export enum ValueType {
-    VALUE_TYPE_UNDEFINED = 0,
-    RAW = 1,
-    DATASET_URI = 2,
-    CSV_URI = 3,
-    PICKLE_URI = 4,
-    PICKLE_BLOB = 5,
-    PLASMA_ID = 6,
-}
-
-export class DatamartSearchParameters implements IDatamartSearchParameters {
-    adapterName?: string | undefined;
-    queryJson?: string | undefined;
-
-    constructor(data?: IDatamartSearchParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.adapterName = data["AdapterName"];
-            this.queryJson = data["QueryJson"];
-        }
-    }
-
-    static fromJS(data: any): DatamartSearchParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new DatamartSearchParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["AdapterName"] = this.adapterName;
-        data["QueryJson"] = this.queryJson;
-        return data;
-    }
-}
-
-export interface IDatamartSearchParameters {
-    adapterName?: string | undefined;
-    queryJson?: string | undefined;
-}
-
-export class DatamartAugmentParameters implements IDatamartAugmentParameters {
-    adapterName?: string | undefined;
-    augmentationJson?: string | undefined;
-    numberOfSamples?: number | undefined;
-    augmentedAdapterName?: string | undefined;
-
-    constructor(data?: IDatamartAugmentParameters) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.adapterName = data["AdapterName"];
-            this.augmentationJson = data["AugmentationJson"];
-            this.numberOfSamples = data["NumberOfSamples"];
-            this.augmentedAdapterName = data["AugmentedAdapterName"];
-        }
-    }
-
-    static fromJS(data: any): DatamartAugmentParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new DatamartAugmentParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["AdapterName"] = this.adapterName;
-        data["AugmentationJson"] = this.augmentationJson;
-        data["NumberOfSamples"] = this.numberOfSamples;
-        data["AugmentedAdapterName"] = this.augmentedAdapterName;
-        return data;
-    }
-}
-
-export interface IDatamartAugmentParameters {
-    adapterName?: string | undefined;
-    augmentationJson?: string | undefined;
-    numberOfSamples?: number | undefined;
-    augmentedAdapterName?: string | undefined;
-}
-
-export class RawDataResult extends DistResult implements IRawDataResult {
-    samples?: { [key: string]: any[]; } | undefined;
-    weightedWords?: { [key: string]: Word[]; } | undefined;
-
-    constructor(data?: IRawDataResult) {
-        super(data);
-        this._discriminator = "RawDataResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Samples"]) {
-                this.samples = {};
-                for (let key in data["Samples"]) {
-                    if (data["Samples"].hasOwnProperty(key))
-                        this.samples[key] = data["Samples"][key] !== undefined ? data["Samples"][key] : [];
-                }
-            }
-            if (data["WeightedWords"]) {
-                this.weightedWords = {};
-                for (let key in data["WeightedWords"]) {
-                    if (data["WeightedWords"].hasOwnProperty(key))
-                        this.weightedWords[key] = data["WeightedWords"][key] ? data["WeightedWords"][key].map((i: any) => Word.fromJS(i)) : [];
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): RawDataResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new RawDataResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.samples) {
-            data["Samples"] = {};
-            for (let key in this.samples) {
-                if (this.samples.hasOwnProperty(key))
-                    data["Samples"][key] = this.samples[key];
-            }
-        }
-        if (this.weightedWords) {
-            data["WeightedWords"] = {};
-            for (let key in this.weightedWords) {
-                if (this.weightedWords.hasOwnProperty(key))
-                    data["WeightedWords"][key] = this.weightedWords[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRawDataResult extends IDistResult {
-    samples?: { [key: string]: any[]; } | undefined;
-    weightedWords?: { [key: string]: Word[]; } | undefined;
-}
-
-export class Word implements IWord {
-    text?: string | undefined;
-    occurrences?: number | undefined;
-    stem?: string | undefined;
-    isWordGroup?: boolean | undefined;
-
-    constructor(data?: IWord) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.text = data["Text"];
-            this.occurrences = data["Occurrences"];
-            this.stem = data["Stem"];
-            this.isWordGroup = data["IsWordGroup"];
-        }
-    }
-
-    static fromJS(data: any): Word {
-        data = typeof data === 'object' ? data : {};
-        let result = new Word();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Text"] = this.text;
-        data["Occurrences"] = this.occurrences;
-        data["Stem"] = this.stem;
-        data["IsWordGroup"] = this.isWordGroup;
-        return data;
-    }
-}
-
-export interface IWord {
-    text?: string | undefined;
-    occurrences?: number | undefined;
-    stem?: string | undefined;
-    isWordGroup?: boolean | undefined;
-}
-
-export class SampleOperationParameters extends DistOperationParameters implements ISampleOperationParameters {
-    numSamples?: number | undefined;
-    attributeParameters?: AttributeParameters[] | undefined;
-    brushes?: string[] | undefined;
-
-    constructor(data?: ISampleOperationParameters) {
-        super(data);
-        this._discriminator = "SampleOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.numSamples = data["NumSamples"];
-            if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
-                this.attributeParameters = [];
-                for (let item of data["AttributeParameters"])
-                    this.attributeParameters.push(AttributeParameters.fromJS(item));
-            }
-            if (data["Brushes"] && data["Brushes"].constructor === Array) {
-                this.brushes = [];
-                for (let item of data["Brushes"])
-                    this.brushes.push(item);
-            }
-        }
-    }
-
-    static fromJS(data: any): SampleOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new SampleOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["NumSamples"] = this.numSamples;
-        if (this.attributeParameters && this.attributeParameters.constructor === Array) {
-            data["AttributeParameters"] = [];
-            for (let item of this.attributeParameters)
-                data["AttributeParameters"].push(item.toJSON());
-        }
-        if (this.brushes && this.brushes.constructor === Array) {
-            data["Brushes"] = [];
-            for (let item of this.brushes)
-                data["Brushes"].push(item);
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISampleOperationParameters extends IDistOperationParameters {
-    numSamples?: number | undefined;
-    attributeParameters?: AttributeParameters[] | undefined;
-    brushes?: string[] | undefined;
-}
-
-export class SampleResult extends DistResult implements ISampleResult {
-    samples?: { [key: string]: { [key: string]: number; }; } | undefined;
-    isTruncated?: boolean | undefined;
-
-    constructor(data?: ISampleResult) {
-        super(data);
-        this._discriminator = "SampleResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Samples"]) {
-                this.samples = {};
-                for (let key in data["Samples"]) {
-                    if (data["Samples"].hasOwnProperty(key))
-                        this.samples[key] = data["Samples"][key] !== undefined ? data["Samples"][key] : {};
-                }
-            }
-            this.isTruncated = data["IsTruncated"];
-        }
-    }
-
-    static fromJS(data: any): SampleResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new SampleResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.samples) {
-            data["Samples"] = {};
-            for (let key in this.samples) {
-                if (this.samples.hasOwnProperty(key))
-                    data["Samples"][key] = this.samples[key];
-            }
-        }
-        data["IsTruncated"] = this.isTruncated;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ISampleResult extends IDistResult {
-    samples?: { [key: string]: { [key: string]: number; }; } | undefined;
-    isTruncated?: boolean | undefined;
-}
-
-export class ResultParameters extends UniqueJson implements IResultParameters {
-    operationReference?: IOperationReference | undefined;
-    stopOperation?: boolean | undefined;
-
-    protected _discriminator: string;
-
-    constructor(data?: IResultParameters) {
-        super(data);
-        this._discriminator = "ResultParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.operationReference = data["OperationReference"] ? IOperationReference.fromJS(data["OperationReference"]) : <any>undefined;
-            this.stopOperation = data["StopOperation"];
-        }
-    }
-
-    static fromJS(data: any): ResultParameters {
-        data = typeof data === 'object' ? data : {};
-        if (data["discriminator"] === "RecommenderResultParameters") {
-            let result = new RecommenderResultParameters();
-            result.init(data);
-            return result;
-        }
-        let result = new ResultParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["discriminator"] = this._discriminator;
-        data["OperationReference"] = this.operationReference ? this.operationReference.toJSON() : <any>undefined;
-        data["StopOperation"] = this.stopOperation;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IResultParameters extends IUniqueJson {
-    operationReference?: IOperationReference | undefined;
-    stopOperation?: boolean | undefined;
-}
-
-export class RecommenderResultParameters extends ResultParameters implements IRecommenderResultParameters {
-    from?: number | undefined;
-    to?: number | undefined;
-    pValueSorting?: Sorting | undefined;
-    effectSizeFilter?: EffectSize | undefined;
-
-    constructor(data?: IRecommenderResultParameters) {
-        super(data);
-        this._discriminator = "RecommenderResultParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.from = data["From"];
-            this.to = data["To"];
-            this.pValueSorting = data["PValueSorting"];
-            this.effectSizeFilter = data["EffectSizeFilter"];
-        }
-    }
-
-    static fromJS(data: any): RecommenderResultParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new RecommenderResultParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["From"] = this.from;
-        data["To"] = this.to;
-        data["PValueSorting"] = this.pValueSorting;
-        data["EffectSizeFilter"] = this.effectSizeFilter;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRecommenderResultParameters extends IResultParameters {
-    from?: number | undefined;
-    to?: number | undefined;
-    pValueSorting?: Sorting | undefined;
-    effectSizeFilter?: EffectSize | undefined;
-}
-
-export enum Sorting {
-    Ascending = "Ascending",
-    Descending = "Descending",
-}
-
-export class AddComparisonParameters extends ModelOperationParameters implements IAddComparisonParameters {
-    modelId?: ModelId | undefined;
-    comparisonOrder?: number | undefined;
-    childOperationParameters?: OperationParameters[] | undefined;
-    isCachable?: boolean | undefined;
-
-    constructor(data?: IAddComparisonParameters) {
-        super(data);
-        this._discriminator = "AddComparisonParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
-            this.comparisonOrder = data["ComparisonOrder"];
-            if (data["ChildOperationParameters"] && data["ChildOperationParameters"].constructor === Array) {
-                this.childOperationParameters = [];
-                for (let item of data["ChildOperationParameters"])
-                    this.childOperationParameters.push(OperationParameters.fromJS(item));
-            }
-            this.isCachable = data["IsCachable"];
-        }
-    }
-
-    static fromJS(data: any): AddComparisonParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new AddComparisonParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
-        data["ComparisonOrder"] = this.comparisonOrder;
-        if (this.childOperationParameters && this.childOperationParameters.constructor === Array) {
-            data["ChildOperationParameters"] = [];
-            for (let item of this.childOperationParameters)
-                data["ChildOperationParameters"].push(item.toJSON());
-        }
-        data["IsCachable"] = this.isCachable;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IAddComparisonParameters extends IModelOperationParameters {
-    modelId?: ModelId | undefined;
-    comparisonOrder?: number | undefined;
-    childOperationParameters?: OperationParameters[] | undefined;
-    isCachable?: boolean | undefined;
-}
-
-export class CDFResult extends DistResult implements ICDFResult {
-    cDF?: { [key: string]: number; } | undefined;
-
-    constructor(data?: ICDFResult) {
-        super(data);
-        this._discriminator = "CDFResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["CDF"]) {
-                this.cDF = {};
-                for (let key in data["CDF"]) {
-                    if (data["CDF"].hasOwnProperty(key))
-                        this.cDF[key] = data["CDF"][key];
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): CDFResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new CDFResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.cDF) {
-            data["CDF"] = {};
-            for (let key in this.cDF) {
-                if (this.cDF.hasOwnProperty(key))
-                    data["CDF"][key] = this.cDF[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ICDFResult extends IDistResult {
-    cDF?: { [key: string]: number; } | undefined;
-}
-
-export class ChiSquaredTestResult extends HypothesisTestResult implements IChiSquaredTestResult {
-    hs_aligned?: TupleOfDoubleAndDouble[] | undefined;
-
-    constructor(data?: IChiSquaredTestResult) {
-        super(data);
-        this._discriminator = "ChiSquaredTestResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["hs_aligned"] && data["hs_aligned"].constructor === Array) {
-                this.hs_aligned = [];
-                for (let item of data["hs_aligned"])
-                    this.hs_aligned.push(TupleOfDoubleAndDouble.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): ChiSquaredTestResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new ChiSquaredTestResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.hs_aligned && this.hs_aligned.constructor === Array) {
-            data["hs_aligned"] = [];
-            for (let item of this.hs_aligned)
-                data["hs_aligned"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IChiSquaredTestResult extends IHypothesisTestResult {
-    hs_aligned?: TupleOfDoubleAndDouble[] | undefined;
-}
-
-export class CorrelationTestResult extends HypothesisTestResult implements ICorrelationTestResult {
-    degreeOfFreedom?: number | undefined;
-    sampleCorrelationCoefficient?: number | undefined;
-    distResult?: EmpiricalDistResult | undefined;
-
-    constructor(data?: ICorrelationTestResult) {
-        super(data);
-        this._discriminator = "CorrelationTestResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.degreeOfFreedom = data["DegreeOfFreedom"];
-            this.sampleCorrelationCoefficient = data["SampleCorrelationCoefficient"];
-            this.distResult = data["DistResult"] ? EmpiricalDistResult.fromJS(data["DistResult"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): CorrelationTestResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new CorrelationTestResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["DegreeOfFreedom"] = this.degreeOfFreedom;
-        data["SampleCorrelationCoefficient"] = this.sampleCorrelationCoefficient;
-        data["DistResult"] = this.distResult ? this.distResult.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ICorrelationTestResult extends IHypothesisTestResult {
-    degreeOfFreedom?: number | undefined;
-    sampleCorrelationCoefficient?: number | undefined;
-    distResult?: EmpiricalDistResult | undefined;
-}
-
-export class EmpiricalDistResult extends DistResult implements IEmpiricalDistResult {
-    marginals?: AttributeParameters[] | undefined;
-    marginalDistParameters?: { [key: string]: DistParameter; } | undefined;
-    jointDistParameter?: JointDistParameter | undefined;
-
-    constructor(data?: IEmpiricalDistResult) {
-        super(data);
-        this._discriminator = "EmpiricalDistResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["Marginals"] && data["Marginals"].constructor === Array) {
-                this.marginals = [];
-                for (let item of data["Marginals"])
-                    this.marginals.push(AttributeParameters.fromJS(item));
-            }
-            if (data["MarginalDistParameters"]) {
-                this.marginalDistParameters = {};
-                for (let key in data["MarginalDistParameters"]) {
-                    if (data["MarginalDistParameters"].hasOwnProperty(key))
-                        this.marginalDistParameters[key] = data["MarginalDistParameters"][key] ? DistParameter.fromJS(data["MarginalDistParameters"][key]) : new DistParameter();
-                }
-            }
-            this.jointDistParameter = data["JointDistParameter"] ? JointDistParameter.fromJS(data["JointDistParameter"]) : <any>undefined;
-        }
-    }
-
-    static fromJS(data: any): EmpiricalDistResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new EmpiricalDistResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.marginals && this.marginals.constructor === Array) {
-            data["Marginals"] = [];
-            for (let item of this.marginals)
-                data["Marginals"].push(item.toJSON());
-        }
-        if (this.marginalDistParameters) {
-            data["MarginalDistParameters"] = {};
-            for (let key in this.marginalDistParameters) {
-                if (this.marginalDistParameters.hasOwnProperty(key))
-                    data["MarginalDistParameters"][key] = this.marginalDistParameters[key];
-            }
-        }
-        data["JointDistParameter"] = this.jointDistParameter ? this.jointDistParameter.toJSON() : <any>undefined;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IEmpiricalDistResult extends IDistResult {
-    marginals?: AttributeParameters[] | undefined;
-    marginalDistParameters?: { [key: string]: DistParameter; } | undefined;
-    jointDistParameter?: JointDistParameter | undefined;
-}
-
-export class DistParameter implements IDistParameter {
-    mean?: number | undefined;
-    moment2?: number | undefined;
-    variance?: number | undefined;
-    varianceEstimate?: number | undefined;
-    min?: number | undefined;
-    max?: number | undefined;
-
-    constructor(data?: IDistParameter) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.mean = data["Mean"];
-            this.moment2 = data["Moment2"];
-            this.variance = data["Variance"];
-            this.varianceEstimate = data["VarianceEstimate"];
-            this.min = data["Min"];
-            this.max = data["Max"];
-        }
-    }
-
-    static fromJS(data: any): DistParameter {
-        data = typeof data === 'object' ? data : {};
-        let result = new DistParameter();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Mean"] = this.mean;
-        data["Moment2"] = this.moment2;
-        data["Variance"] = this.variance;
-        data["VarianceEstimate"] = this.varianceEstimate;
-        data["Min"] = this.min;
-        data["Max"] = this.max;
-        return data;
-    }
-}
-
-export interface IDistParameter {
-    mean?: number | undefined;
-    moment2?: number | undefined;
-    variance?: number | undefined;
-    varianceEstimate?: number | undefined;
-    min?: number | undefined;
-    max?: number | undefined;
-}
-
-export class JointDistParameter implements IJointDistParameter {
-    jointDist?: DistParameter | undefined;
-    covariance?: number | undefined;
-
-    constructor(data?: IJointDistParameter) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.jointDist = data["JointDist"] ? DistParameter.fromJS(data["JointDist"]) : <any>undefined;
-            this.covariance = data["Covariance"];
-        }
-    }
-
-    static fromJS(data: any): JointDistParameter {
-        data = typeof data === 'object' ? data : {};
-        let result = new JointDistParameter();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["JointDist"] = this.jointDist ? this.jointDist.toJSON() : <any>undefined;
-        data["Covariance"] = this.covariance;
-        return data;
-    }
-}
-
-export interface IJointDistParameter {
-    jointDist?: DistParameter | undefined;
-    covariance?: number | undefined;
-}
-
-export enum DistributionType {
-    Continuous = 0,
-    Discrete = 1,
-}
-
-export abstract class DistributionTypeExtension implements IDistributionTypeExtension {
-
-    constructor(data?: IDistributionTypeExtension) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): DistributionTypeExtension {
-        data = typeof data === 'object' ? data : {};
-        throw new Error("The abstract class 'DistributionTypeExtension' cannot be instantiated.");
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        return data;
-    }
-}
-
-export interface IDistributionTypeExtension {
-}
-
-export class GetModelStateParameters extends ModelOperationParameters implements IGetModelStateParameters {
-    modelId?: ModelId | undefined;
-    comparisonIds?: ComparisonId[] | undefined;
-    riskControlType?: RiskControlType | undefined;
-
-    constructor(data?: IGetModelStateParameters) {
-        super(data);
-        this._discriminator = "GetModelStateParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
-            if (data["ComparisonIds"] && data["ComparisonIds"].constructor === Array) {
-                this.comparisonIds = [];
-                for (let item of data["ComparisonIds"])
-                    this.comparisonIds.push(ComparisonId.fromJS(item));
-            }
-            this.riskControlType = data["RiskControlType"];
-        }
-    }
-
-    static fromJS(data: any): GetModelStateParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new GetModelStateParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
-        if (this.comparisonIds && this.comparisonIds.constructor === Array) {
-            data["ComparisonIds"] = [];
-            for (let item of this.comparisonIds)
-                data["ComparisonIds"].push(item.toJSON());
-        }
-        data["RiskControlType"] = this.riskControlType;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IGetModelStateParameters extends IModelOperationParameters {
-    modelId?: ModelId | undefined;
-    comparisonIds?: ComparisonId[] | undefined;
-    riskControlType?: RiskControlType | undefined;
-}
-
-export class KSTestResult extends HypothesisTestResult implements IKSTestResult {
-
-    constructor(data?: IKSTestResult) {
-        super(data);
-        this._discriminator = "KSTestResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): KSTestResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new KSTestResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IKSTestResult extends IHypothesisTestResult {
-}
-
-export class ModelWealthParameters extends UniqueJson implements IModelWealthParameters {
-    modelId?: ModelId | undefined;
-    riskControlType?: RiskControlType | undefined;
-
-    constructor(data?: IModelWealthParameters) {
-        super(data);
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
-            this.riskControlType = data["RiskControlType"];
-        }
-    }
-
-    static fromJS(data: any): ModelWealthParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new ModelWealthParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
-        data["RiskControlType"] = this.riskControlType;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IModelWealthParameters extends IUniqueJson {
-    modelId?: ModelId | undefined;
-    riskControlType?: RiskControlType | undefined;
-}
-
-export class RootMeanSquareTestResult extends HypothesisTestResult implements IRootMeanSquareTestResult {
-    simulationCount?: number | undefined;
-    extremeSimulationCount?: number | undefined;
-
-    constructor(data?: IRootMeanSquareTestResult) {
-        super(data);
-        this._discriminator = "RootMeanSquareTestResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.simulationCount = data["SimulationCount"];
-            this.extremeSimulationCount = data["ExtremeSimulationCount"];
-        }
-    }
-
-    static fromJS(data: any): RootMeanSquareTestResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new RootMeanSquareTestResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["SimulationCount"] = this.simulationCount;
-        data["ExtremeSimulationCount"] = this.extremeSimulationCount;
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IRootMeanSquareTestResult extends IHypothesisTestResult {
-    simulationCount?: number | undefined;
-    extremeSimulationCount?: number | undefined;
-}
-
-export class TTestResult extends HypothesisTestResult implements ITTestResult {
-    degreeOfFreedom?: number | undefined;
-    distResults?: EmpiricalDistResult[] | undefined;
-
-    constructor(data?: ITTestResult) {
-        super(data);
-        this._discriminator = "TTestResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.degreeOfFreedom = data["DegreeOfFreedom"];
-            if (data["DistResults"] && data["DistResults"].constructor === Array) {
-                this.distResults = [];
-                for (let item of data["DistResults"])
-                    this.distResults.push(EmpiricalDistResult.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): TTestResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new TTestResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["DegreeOfFreedom"] = this.degreeOfFreedom;
-        if (this.distResults && this.distResults.constructor === Array) {
-            data["DistResults"] = [];
-            for (let item of this.distResults)
-                data["DistResults"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface ITTestResult extends IHypothesisTestResult {
-    degreeOfFreedom?: number | undefined;
-    distResults?: EmpiricalDistResult[] | undefined;
-}
-
-export enum Sorting2 {
-    Ascending = 0,
-    Descending = 1,
-}
-
-export class BinLabel implements IBinLabel {
-    value?: number | undefined;
-    minValue?: number | undefined;
-    maxValue?: number | undefined;
-    label?: string | undefined;
-
-    constructor(data?: IBinLabel) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.value = data["Value"];
-            this.minValue = data["MinValue"];
-            this.maxValue = data["MaxValue"];
-            this.label = data["Label"];
-        }
-    }
-
-    static fromJS(data: any): BinLabel {
-        data = typeof data === 'object' ? data : {};
-        let result = new BinLabel();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        data["MinValue"] = this.minValue;
-        data["MaxValue"] = this.maxValue;
-        data["Label"] = this.label;
-        return data;
-    }
-}
-
-export interface IBinLabel {
-    value?: number | undefined;
-    minValue?: number | undefined;
-    maxValue?: number | undefined;
-    label?: string | undefined;
-}
-
-export class PreProcessedString implements IPreProcessedString {
-    value?: string | undefined;
-    id?: number | undefined;
-    stringLookup?: { [key: string]: number; } | undefined;
-    indexLookup?: { [key: string]: string; } | undefined;
-
-    constructor(data?: IPreProcessedString) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.value = data["Value"];
-            this.id = data["Id"];
-            if (data["StringLookup"]) {
-                this.stringLookup = {};
-                for (let key in data["StringLookup"]) {
-                    if (data["StringLookup"].hasOwnProperty(key))
-                        this.stringLookup[key] = data["StringLookup"][key];
-                }
-            }
-            if (data["IndexLookup"]) {
-                this.indexLookup = {};
-                for (let key in data["IndexLookup"]) {
-                    if (data["IndexLookup"].hasOwnProperty(key))
-                        this.indexLookup[key] = data["IndexLookup"][key];
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): PreProcessedString {
-        data = typeof data === 'object' ? data : {};
-        let result = new PreProcessedString();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Value"] = this.value;
-        data["Id"] = this.id;
-        if (this.stringLookup) {
-            data["StringLookup"] = {};
-            for (let key in this.stringLookup) {
-                if (this.stringLookup.hasOwnProperty(key))
-                    data["StringLookup"][key] = this.stringLookup[key];
-            }
-        }
-        if (this.indexLookup) {
-            data["IndexLookup"] = {};
-            for (let key in this.indexLookup) {
-                if (this.indexLookup.hasOwnProperty(key))
-                    data["IndexLookup"][key] = this.indexLookup[key];
-            }
-        }
-        return data;
-    }
-}
-
-export interface IPreProcessedString {
-    value?: string | undefined;
-    id?: number | undefined;
-    stringLookup?: { [key: string]: number; } | undefined;
-    indexLookup?: { [key: string]: string; } | undefined;
-}
-
-export class BitSet implements IBitSet {
-    length?: number | undefined;
-    size?: number | undefined;
-
-    constructor(data?: IBitSet) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-            this.length = data["Length"];
-            this.size = data["Size"];
-        }
-    }
-
-    static fromJS(data: any): BitSet {
-        data = typeof data === 'object' ? data : {};
-        let result = new BitSet();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Length"] = this.length;
-        data["Size"] = this.size;
-        return data;
-    }
-}
-
-export interface IBitSet {
-    length?: number | undefined;
-    size?: number | undefined;
-}
-
-export class DateTimeUtil implements IDateTimeUtil {
-
-    constructor(data?: IDateTimeUtil) {
-        if (data) {
-            for (var property in data) {
-                if (data.hasOwnProperty(property))
-                    (<any>this)[property] = (<any>data)[property];
-            }
-        }
-    }
-
-    init(data?: any) {
-        if (data) {
-        }
-    }
-
-    static fromJS(data: any): DateTimeUtil {
-        data = typeof data === 'object' ? data : {};
-        let result = new DateTimeUtil();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        return data;
-    }
-}
-
-export interface IDateTimeUtil {
-}
-
-export class FrequentItemsetOperationParameters extends DistOperationParameters implements IFrequentItemsetOperationParameters {
-    filter?: string | undefined;
-    attributeParameters?: AttributeParameters[] | undefined;
-    attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
-
-    constructor(data?: IFrequentItemsetOperationParameters) {
-        super(data);
-        this._discriminator = "FrequentItemsetOperationParameters";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            this.filter = data["Filter"];
-            if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
-                this.attributeParameters = [];
-                for (let item of data["AttributeParameters"])
-                    this.attributeParameters.push(AttributeParameters.fromJS(item));
-            }
-            if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) {
-                this.attributeCodeParameters = [];
-                for (let item of data["AttributeCodeParameters"])
-                    this.attributeCodeParameters.push(AttributeCaclculatedParameters.fromJS(item));
-            }
-        }
-    }
-
-    static fromJS(data: any): FrequentItemsetOperationParameters {
-        data = typeof data === 'object' ? data : {};
-        let result = new FrequentItemsetOperationParameters();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        data["Filter"] = this.filter;
-        if (this.attributeParameters && this.attributeParameters.constructor === Array) {
-            data["AttributeParameters"] = [];
-            for (let item of this.attributeParameters)
-                data["AttributeParameters"].push(item.toJSON());
-        }
-        if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) {
-            data["AttributeCodeParameters"] = [];
-            for (let item of this.attributeCodeParameters)
-                data["AttributeCodeParameters"].push(item.toJSON());
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IFrequentItemsetOperationParameters extends IDistOperationParameters {
-    filter?: string | undefined;
-    attributeParameters?: AttributeParameters[] | undefined;
-    attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
-}
-
-export class FrequentItemsetResult extends Result implements IFrequentItemsetResult {
-    frequentItems?: { [key: string]: number; } | undefined;
-
-    constructor(data?: IFrequentItemsetResult) {
-        super(data);
-        this._discriminator = "FrequentItemsetResult";
-    }
-
-    init(data?: any) {
-        super.init(data);
-        if (data) {
-            if (data["FrequentItems"]) {
-                this.frequentItems = {};
-                for (let key in data["FrequentItems"]) {
-                    if (data["FrequentItems"].hasOwnProperty(key))
-                        this.frequentItems[key] = data["FrequentItems"][key];
-                }
-            }
-        }
-    }
-
-    static fromJS(data: any): FrequentItemsetResult {
-        data = typeof data === 'object' ? data : {};
-        let result = new FrequentItemsetResult();
-        result.init(data);
-        return result;
-    }
-
-    toJSON(data?: any) {
-        data = typeof data === 'object' ? data : {};
-        if (this.frequentItems) {
-            data["FrequentItems"] = {};
-            for (let key in this.frequentItems) {
-                if (this.frequentItems.hasOwnProperty(key))
-                    data["FrequentItems"][key] = this.frequentItems[key];
-            }
-        }
-        super.toJSON(data);
-        return data;
-    }
-}
-
-export interface IFrequentItemsetResult extends IResult {
-    frequentItems?: { [key: string]: number; } | undefined;
-}
-
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
deleted file mode 100644
index 013f2244e..000000000
--- a/src/client/northstar/operations/BaseOperation.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-import { FilterModel } from '../core/filter/FilterModel';
-import { ErrorResult, Exception, OperationParameters, OperationReference, Result, ResultParameters } from '../model/idea/idea';
-import { action, computed, observable } from "mobx";
-import { Gateway } from '../manager/Gateway';
-
-export abstract class BaseOperation {
-    private _interactionTimeoutId: number = 0;
-    private static _currentOperations: Map<number, PollPromise> = new Map<number, PollPromise>();
-    //public InteractionTimeout: EventDelegate<InteractionTimeoutEventArgs> = new EventDelegate<InteractionTimeoutEventArgs>();
-
-    @observable public Error: string = "";
-    @observable public OverridingFilters: FilterModel[] = [];
-    //@observable 
-    @observable public Result?: Result = undefined;
-    @observable public ComputationStarted: boolean = false;
-    public OperationReference?: OperationReference = undefined;
-
-    private static _nextId = 0;
-    public RequestSalt: string = "";
-    public Id: number;
-
-    constructor() {
-        this.Id = BaseOperation._nextId++;
-    }
-
-    @computed
-    public get FilterString(): string {
-        return "";
-    }
-
-
-    @action
-    public SetResult(result: Result): void {
-        this.Result = result;
-    }
-
-    public async Update(): Promise<void> {
-
-        try {
-            if (BaseOperation._currentOperations.has(this.Id)) {
-                BaseOperation._currentOperations.get(this.Id)!.Cancel();
-                if (this.OperationReference) {
-                    Gateway.Instance.PauseOperation(this.OperationReference.toJSON());
-                }
-            }
-
-            const operationParameters = this.CreateOperationParameters();
-            if (this.Result) {
-                this.Result.progress = 0;
-            } // bcz: used to set Result to undefined, but that causes the display to blink
-            this.Error = "";
-            const salt = Math.random().toString();
-            this.RequestSalt = salt;
-
-            if (!operationParameters) {
-                this.ComputationStarted = false;
-                return;
-            }
-
-            this.ComputationStarted = true;
-            //let start = performance.now();
-            const promise = Gateway.Instance.StartOperation(operationParameters.toJSON());
-            promise.catch(err => {
-                action(() => {
-                    this.Error = err;
-                    console.error(err);
-                });
-            });
-            const operationReference = await promise;
-
-
-            if (operationReference) {
-                this.OperationReference = operationReference;
-
-                const resultParameters = new ResultParameters();
-                resultParameters.operationReference = operationReference;
-
-                const pollPromise = new PollPromise(salt, operationReference);
-                BaseOperation._currentOperations.set(this.Id, pollPromise);
-
-                pollPromise.Start(async () => {
-                    const result = await Gateway.Instance.GetResult(resultParameters.toJSON());
-                    if (result instanceof ErrorResult) {
-                        throw new Error((result).message);
-                    }
-                    if (this.RequestSalt === pollPromise.RequestSalt) {
-                        if (result && (!this.Result || this.Result.progress !== result.progress)) {
-                            /*if (operationViewModel.Result !== null && operationViewModel.Result !== undefined) {
-                                let t1 = performance.now();
-                                console.log((t1 - start) + " milliseconds.");
-                                start = performance.now();
-                            }*/
-                            this.SetResult(result);
-                        }
-
-                        if (!result || result.progress! < 1) {
-                            return true;
-                        }
-                    }
-                    return false;
-                }, 100).catch((err: Error) => action(() => {
-                    this.Error = err.message;
-                    console.error(err.message);
-                })()
-                );
-            }
-        }
-        catch (err) {
-            console.error(err as Exception);
-            // ErrorDialog.Instance.HandleError(err, operationViewModel);
-        }
-    }
-
-    public CreateOperationParameters(): OperationParameters | undefined { return undefined; }
-
-    private interactionTimeout() {
-        // clearTimeout(this._interactionTimeoutId);
-        // this.InteractionTimeout.Fire(new InteractionTimeoutEventArgs(this.TypedViewModel, InteractionTimeoutType.Timeout));
-    }
-}
-
-export class PollPromise {
-    public RequestSalt: string;
-    public OperationReference: OperationReference;
-
-    private _notCanceled: boolean = true;
-    private _poll: undefined | (() => Promise<boolean>);
-    private _delay: number = 0;
-
-    public constructor(requestKey: string, operationReference: OperationReference) {
-        this.RequestSalt = requestKey;
-        this.OperationReference = operationReference;
-    }
-
-    public Cancel(): void {
-        this._notCanceled = false;
-    }
-
-    public Start(poll: () => Promise<boolean>, delay: number): Promise<void> {
-        this._poll = poll;
-        this._delay = delay;
-        return this.pollRecursive();
-    }
-
-    private pollRecursive = (): Promise<void> => {
-        return Promise.resolve().then(this._poll).then((flag) => {
-            this._notCanceled && flag && new Promise((res) => (setTimeout(res, this._delay)))
-                .then(this.pollRecursive);
-        });
-    }
-}
-
-
-export class InteractionTimeoutEventArgs {
-    constructor(public Sender: object, public Type: InteractionTimeoutType) {
-    }
-}
-
-export enum InteractionTimeoutType {
-    Reset = 0,
-    Timeout = 1
-}
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
deleted file mode 100644
index 74e23ea48..000000000
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { action, computed, observable, trace } from "mobx";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
-import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
-import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttributeModel";
-import { FilterModel } from "../core/filter/FilterModel";
-import { FilterOperand } from "../core/filter/FilterOperand";
-import { IBaseFilterConsumer } from "../core/filter/IBaseFilterConsumer";
-import { IBaseFilterProvider } from "../core/filter/IBaseFilterProvider";
-import { HistogramField } from "../dash-fields/HistogramField";
-import { SETTINGS_SAMPLE_SIZE, SETTINGS_X_BINS, SETTINGS_Y_BINS } from "../model/binRanges/VisualBinRangeHelper";
-import { AggregateFunction, AggregateParameters, Attribute, AverageAggregateParameters, Bin, DataType, DoubleValueAggregateResult, HistogramOperationParameters, HistogramResult, QuantitativeBinRange } from "../model/idea/idea";
-import { ModelHelpers } from "../model/ModelHelpers";
-import { ArrayUtil } from "../utils/ArrayUtil";
-import { BaseOperation } from "./BaseOperation";
-import { Doc } from "../../../new_fields/Doc";
-import { Cast, NumCast } from "../../../new_fields/Types";
-
-export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
-    public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
-    @observable public FilterOperand: FilterOperand = FilterOperand.AND;
-    @observable public Links: Doc[] = [];
-    @observable public BrushLinks: { l: Doc, b: Doc }[] = [];
-    @observable public BrushColors: number[] = [];
-    @observable public BarFilterModels: FilterModel[] = [];
-
-    @observable public Normalization: number = -1;
-    @observable public X: AttributeTransformationModel;
-    @observable public Y: AttributeTransformationModel;
-    @observable public V: AttributeTransformationModel;
-    @observable public SchemaName: string;
-    @observable public QRange: QuantitativeBinRange | undefined;
-    public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
-
-    constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
-        super();
-        this.X = x;
-        this.Y = y;
-        this.V = v;
-        this.Normalization = normalized ? normalized : -1;
-        this.SchemaName = schemaName;
-    }
-
-    public static Duplicate(op: HistogramOperation) {
-
-        return new HistogramOperation(op.SchemaName, op.X, op.Y, op.V, op.Normalization);
-    }
-    public Copy(): HistogramOperation {
-        return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization);
-    }
-
-    Equals(other: Object): boolean {
-        throw new Error("Method not implemented.");
-    }
-
-
-    public get FilterModels() {
-        return this.BarFilterModels;
-    }
-    @action
-    public AddFilterModels(filterModels: FilterModel[]): void {
-        filterModels.filter(f => f !== null).forEach(fm => this.BarFilterModels.push(fm));
-    }
-    @action
-    public RemoveFilterModels(filterModels: FilterModel[]): void {
-        ArrayUtil.RemoveMany(this.BarFilterModels, filterModels);
-    }
-
-    @computed
-    public get FilterString(): string {
-        if (this.OverridingFilters.length > 0) {
-            return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
-        }
-        let filterModels: FilterModel[] = [];
-        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
-    }
-
-    public get BrushString(): string[] {
-        let brushes: string[] = [];
-        this.BrushLinks.map(brushLink => {
-            let brushHistogram = Cast(brushLink.b.data, HistogramField);
-            if (brushHistogram) {
-                let filterModels: FilterModel[] = [];
-                brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.HistoOp, new Set<IBaseFilterProvider>(), filterModels, false));
-            }
-        });
-        return brushes;
-    }
-
-    _stackedFilters: (FilterModel[])[] = [];
-    @action
-    public DrillDown(up: boolean) {
-        if (!up) {
-            if (!this.BarFilterModels.length) {
-                return;
-            }
-            this._stackedFilters.push(this.BarFilterModels.map(f => f));
-            this.OverridingFilters.length = 0;
-            this.OverridingFilters.push(...this._stackedFilters[this._stackedFilters.length - 1]);
-            this.BarFilterModels.map(fm => fm).map(fm => this.RemoveFilterModels([fm]));
-            //this.updateHistogram();
-        } else {
-            this.OverridingFilters.length = 0;
-            if (this._stackedFilters.length) {
-                this.OverridingFilters.push(...this._stackedFilters.pop()!);
-            }
-            // else 
-            //     this.updateHistogram();
-        }
-    }
-
-    private getAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) {
-        let allAttributes = new Array<AttributeTransformationModel>(histoX, histoY, histoValue);
-        allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
-
-        let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float];
-        let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(this.Schema!.distinctAttributeParameters, allAttributes);
-        let globalAggregateParameters: AggregateParameters[] = [];
-        [histoX, histoY]
-            .filter(a => a.AggregateFunction === AggregateFunction.None && ArrayUtil.Contains(numericDataTypes, a.AttributeModel.DataType))
-            .forEach(a => {
-                let avg = new AverageAggregateParameters();
-                avg.attributeParameters = ModelHelpers.GetAttributeParameters(a.AttributeModel);
-                globalAggregateParameters.push(avg);
-            });
-        return [perBinAggregateParameters, globalAggregateParameters];
-    }
-
-    public CreateOperationParameters(): HistogramOperationParameters | undefined {
-        if (this.X && this.Y && this.V) {
-            let [perBinAggregateParameters, globalAggregateParameters] = this.getAggregateParameters(this.X, this.Y, this.V);
-            return new HistogramOperationParameters({
-                enableBrushComputation: true,
-                adapterName: this.SchemaName,
-                filter: this.FilterString,
-                brushes: this.BrushString,
-                binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined),
-                ModelHelpers.GetBinningParameters(this.Y, SETTINGS_Y_BINS)],
-                sampleStreamBlockSize: SETTINGS_SAMPLE_SIZE,
-                perBinAggregateParameters: perBinAggregateParameters,
-                globalAggregateParameters: globalAggregateParameters,
-                sortPerBinAggregateParameter: undefined,
-                attributeCalculatedParameters: CalculatedAttributeManager
-                    .AllCalculatedAttributes.map(a => ModelHelpers.GetAttributeParametersFromAttributeModel(a)),
-                degreeOfParallism: 1, // Settings.Instance.DegreeOfParallelism,
-                isCachable: false
-            });
-        }
-    }
-
-    @action
-    public async Update(): Promise<void> {
-        this.BrushColors = this.BrushLinks.map(e => NumCast(e.l.backgroundColor));
-        return super.Update();
-    }
-}
-
-
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
deleted file mode 100644
index 12b8d8e77..000000000
--- a/src/client/northstar/utils/ArrayUtil.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { Exception } from "../model/idea/idea";
-
-export class ArrayUtil {
-
-    public static Contains(arr1: any[], arr2: any): boolean {
-        if (arr1.length === 0) {
-            return false;
-        }
-        let isComplex = typeof arr1[0] === "object";
-        for (const ele of arr1) {
-            if (isComplex && "Equals" in ele) {
-                if (ele.Equals(arr2)) {
-                    return true;
-                }
-            }
-            else {
-                if (ele === arr2) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-
-    public static RemoveMany(arr: any[], elements: Object[]) {
-        elements.forEach(e => ArrayUtil.Remove(arr, e));
-    }
-
-    public static AddMany(arr: any[], others: Object[]) {
-        arr.push(...others);
-    }
-
-    public static Clear(arr: any[]) {
-        arr.splice(0, arr.length);
-    }
-
-
-    public static Remove(arr: any[], other: Object) {
-        const index = ArrayUtil.IndexOfWithEqual(arr, other);
-        if (index === -1) {
-            return;
-        }
-        arr.splice(index, 1);
-    }
-
-
-    public static First<T>(arr: T[], predicate: (x: any) => boolean): T {
-        let filtered = arr.filter(predicate);
-        if (filtered.length > 0) {
-            return filtered[0];
-        }
-        throw new Exception();
-    }
-
-    public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined {
-        let filtered = arr.filter(predicate);
-        if (filtered.length > 0) {
-            return filtered[0];
-        }
-        return undefined;
-    }
-
-    public static Distinct(arr: any[]): any[] {
-        let ret = [];
-        for (const ele of arr) {
-            if (!ArrayUtil.Contains(ret, ele)) {
-                ret.push(ele);
-            }
-        }
-        return ret;
-    }
-
-    public static IndexOfWithEqual(arr: any[], other: any): number {
-        for (let i = 0; i < arr.length; i++) {
-            let isComplex = typeof arr[0] === "object";
-            if (isComplex && "Equals" in arr[i]) {
-                if (arr[i].Equals(other)) {
-                    return i;
-                }
-            }
-            else {
-                if (arr[i] === other) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts
deleted file mode 100644
index df14d4da0..000000000
--- a/src/client/northstar/utils/Extensions.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-interface String {
-    ReplaceAll(toReplace: string, replacement: string): string;
-    Truncate(length: number, replacement: string): String;
-}
-
-String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string {
-    var target = this;
-    return target.split(toReplace).join(replacement);
-};
-
-String.prototype.Truncate = function (length: number, replacement: string): String {
-    var target = this;
-    if (target.length >= length) {
-        target = target.slice(0, Math.max(0, length - replacement.length)) + replacement;
-    }
-    return target;
-};
-
-interface Math {
-    log10(val: number): number;
-}
-
-Math.log10 = function (val: number): number {
-    return Math.log(val) / Math.LN10;
-};
-
-declare interface ObjectConstructor {
-    assign(...objects: Object[]): Object;
-}
diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts
deleted file mode 100644
index d5220c479..000000000
--- a/src/client/northstar/utils/GeometryUtil.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { MathUtil, PIXIRectangle, PIXIPoint } from "./MathUtil";
-
-
-export class GeometryUtil {
-
-    public static ComputeBoundingBox(points: { x: number, y: number }[], scale = 1, padding: number = 0): PIXIRectangle {
-        let minX: number = Number.MAX_VALUE;
-        let minY: number = Number.MAX_VALUE;
-        let maxX: number = Number.MIN_VALUE;
-        let maxY: number = Number.MIN_VALUE;
-        for (const point of points) {
-            if (point.x < minX) {
-                minX = point.x;
-            }
-            if (point.y < minY) {
-                minY = point.y;
-            }
-            if (point.x > maxX) {
-                maxX = point.x;
-            }
-            if (point.y > maxY) {
-                maxY = point.y;
-            }
-        }
-        return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2);
-    }
-
-    public static RectangleOverlap(rect1: PIXIRectangle, rect2: PIXIRectangle) {
-        let x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
-        let y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
-        return x_overlap * y_overlap;
-    }
-
-    public static RotatePoints(center: { x: number, y: number }, points: { x: number, y: number }[], angle: number): PIXIPoint[] {
-        const rotate = (cx: number, cy: number, x: number, y: number, angle: number) => {
-            const radians = angle,
-                cos = Math.cos(radians),
-                sin = Math.sin(radians),
-                nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
-                ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
-            return new PIXIPoint(nx, ny);
-        };
-        return points.map(p => rotate(center.x, center.y, p.x, p.y, angle));
-    }
-
-    public static LineByLeastSquares(points: { x: number, y: number }[]): PIXIPoint[] {
-        let sum_x: number = 0;
-        let sum_y: number = 0;
-        let sum_xy: number = 0;
-        let sum_xx: number = 0;
-        let count: number = 0;
-
-        let x: number = 0;
-        let y: number = 0;
-
-
-        if (points.length === 0) {
-            return [];
-        }
-
-        for (const point of points) {
-            x = point.x;
-            y = point.y;
-            sum_x += x;
-            sum_y += y;
-            sum_xx += x * x;
-            sum_xy += x * y;
-            count++;
-        }
-
-        let m = (count * sum_xy - sum_x * sum_y) / (count * sum_xx - sum_x * sum_x);
-        let b = (sum_y / count) - (m * sum_x) / count;
-        let result: PIXIPoint[] = new Array<PIXIPoint>();
-
-        for (const point of points) {
-            x = point.x;
-            y = x * m + b;
-            result.push(new PIXIPoint(x, y));
-        }
-        return result;
-    }
-
-    // public static PointInsidePolygon(vs:Point[], x:number, y:number):boolean {
-    //     // ray-casting algorithm based on
-    //     // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
-
-    //     var inside = false;
-    //     for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
-    //         var xi = vs[i].x, yi = vs[i].y;
-    //         var xj = vs[j].x, yj = vs[j].y;
-
-    //         var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
-    //         if (intersect) 
-    //            inside = !inside;
-    //     }
-
-    //     return inside;
-    // };
-
-    public static IntersectLines(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number): boolean {
-        let a1: number, a2: number, b1: number, b2: number, c1: number, c2: number;
-        let r1: number, r2: number, r3: number, r4: number;
-        let denom: number, offset: number, num: number;
-
-        a1 = y2 - y1;
-        b1 = x1 - x2;
-        c1 = (x2 * y1) - (x1 * y2);
-        r3 = ((a1 * x3) + (b1 * y3) + c1);
-        r4 = ((a1 * x4) + (b1 * y4) + c1);
-
-        if ((r3 !== 0) && (r4 !== 0) && (MathUtil.Sign(r3) === MathUtil.Sign(r4))) {
-            return false;
-        }
-
-        a2 = y4 - y3;
-        b2 = x3 - x4;
-        c2 = (x4 * y3) - (x3 * y4);
-
-        r1 = (a2 * x1) + (b2 * y1) + c2;
-        r2 = (a2 * x2) + (b2 * y2) + c2;
-
-        if ((r1 !== 0) && (r2 !== 0) && (MathUtil.Sign(r1) === MathUtil.Sign(r2))) {
-            return false;
-        }
-
-        denom = (a1 * b2) - (a2 * b1);
-
-        if (denom === 0) {
-            return false;
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/IDisposable.ts b/src/client/northstar/utils/IDisposable.ts
deleted file mode 100644
index 5e9843326..000000000
--- a/src/client/northstar/utils/IDisposable.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface IDisposable {
-    Dispose(): void;
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/IEquatable.ts b/src/client/northstar/utils/IEquatable.ts
deleted file mode 100644
index 2f81c2478..000000000
--- a/src/client/northstar/utils/IEquatable.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface IEquatable {
-    Equals(other: Object): boolean;
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/KeyCodes.ts b/src/client/northstar/utils/KeyCodes.ts
deleted file mode 100644
index 044569ffe..000000000
--- a/src/client/northstar/utils/KeyCodes.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-/**
- * Class contains the keycodes for keys on your keyboard.
- *
- * Useful for auto completion:
- *
- * ```
- * switch (event.key)
- * {
- *      case KeyCode.UP:
- *      {
- *      	// Up key pressed
- *       	break;
- *      }
- *      case KeyCode.DOWN:
- *      {
- *      	// Down key pressed
- *       	break;
- *      }
- *      case KeyCode.LEFT:
- *      {
- *      	// Left key pressed
- *       	break;
- *      }
- *      case KeyCode.RIGHT:
- *      {
- *      	// Right key pressed
- *       	break;
- *      }
- *      default:
- *      {
- *      	// ignore
- *      	break;
- *      }
- * }
- * ```
- */
-export class KeyCodes
-{
-	public static TAB:number = 9;
-	public static CAPS_LOCK:number = 20;
-	public static SHIFT:number = 16;
-	public static CONTROL:number = 17;
-	public static SPACE:number = 32;
-	public static DOWN:number = 40;
-	public static UP:number = 38;
-	public static LEFT:number = 37;
-	public static RIGHT:number = 39;
-	public static ESCAPE:number = 27;
-	public static F1:number = 112;
-	public static F2:number = 113;
-	public static F3:number = 114;
-	public static F4:number = 115;
-	public static F5:number = 116;
-	public static F6:number = 117;
-	public static F7:number = 118;
-	public static F8:number = 119;
-	public static F9:number = 120;
-	public static F10:number = 121;
-	public static F11:number = 122;
-	public static F12:number = 123;
-	public static INSERT:number = 45;
-	public static HOME:number = 36;
-	public static PAGE_UP:number = 33;
-	public static PAGE_DOWN:number = 34;
-	public static DELETE:number = 46;
-	public static END:number = 35;
-	public static ENTER:number = 13;
-	public static BACKSPACE:number = 8;
-	public static NUMPAD_0:number = 96;
-	public static NUMPAD_1:number = 97;
-	public static NUMPAD_2:number = 98;
-	public static NUMPAD_3:number = 99;
-	public static NUMPAD_4:number = 100;
-	public static NUMPAD_5:number = 101;
-	public static NUMPAD_6:number = 102;
-	public static NUMPAD_7:number = 103;
-	public static NUMPAD_8:number = 104;
-	public static NUMPAD_9:number = 105;
-	public static NUMPAD_DIVIDE:number = 111;
-	public static NUMPAD_ADD:number = 107;
-	public static NUMPAD_ENTER:number = 13;
-	public static NUMPAD_DECIMAL:number = 110;
-	public static NUMPAD_SUBTRACT:number = 109;
-	public static NUMPAD_MULTIPLY:number = 106;
-	public static SEMICOLON:number = 186;
-	public static EQUAL:number = 187;
-	public static COMMA:number = 188;
-	public static MINUS:number = 189;
-	public static PERIOD:number = 190;
-	public static SLASH:number = 191;
-	public static BACKQUOTE:number = 192;
-	public static LEFTBRACKET:number = 219;
-	public static BACKSLASH:number = 220;
-	public static RIGHTBRACKET:number = 221;
-	public static QUOTE:number = 222;
-	public static ALT:number = 18;
-	public static COMMAND:number = 15;
-	public static NUMPAD:number = 21;
-	public static A:number = 65;
-	public static B:number = 66;
-	public static C:number = 67;
-	public static D:number = 68;
-	public static E:number = 69;
-	public static F:number = 70;
-	public static G:number = 71;
-	public static H:number = 72;
-	public static I:number = 73;
-	public static J:number = 74;
-	public static K:number = 75;
-	public static L:number = 76;
-	public static M:number = 77;
-	public static N:number = 78;
-	public static O:number = 79;
-	public static P:number = 80;
-	public static Q:number = 81;
-	public static R:number = 82;
-	public static S:number = 83;
-	public static T:number = 84;
-	public static U:number = 85;
-	public static V:number = 86;
-	public static W:number = 87;
-	public static X:number = 88;
-	public static Y:number = 89;
-	public static Z:number = 90;
-	public static NUM_0:number = 48;
-	public static NUM_1:number = 49;
-	public static NUM_2:number = 50;
-	public static NUM_3:number = 51;
-	public static NUM_4:number = 52;
-	public static NUM_5:number = 53;
-	public static NUM_6:number = 54;
-	public static NUM_7:number = 55;
-	public static NUM_8:number = 56;
-	public static NUM_9:number = 57;
-	public static SUBSTRACT:number = 189;
-	public static ADD:number = 187;
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/LABColor.ts b/src/client/northstar/utils/LABColor.ts
deleted file mode 100644
index 72e46fb7f..000000000
--- a/src/client/northstar/utils/LABColor.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-
-export class LABColor {
-    public L: number;
-    public A: number;
-    public B: number;
-
-    // constructor - takes three floats for lightness and color-opponent dimensions
-    constructor(l: number, a: number, b: number) {
-        this.L = l;
-        this.A = a;
-        this.B = b;
-    }
-
-    // static function for linear interpolation between two LABColors
-    public static Lerp(a: LABColor, b: LABColor, t: number): LABColor {
-        return new LABColor(LABColor.LerpNumber(a.L, b.L, t), LABColor.LerpNumber(a.A, b.A, t), LABColor.LerpNumber(a.B, b.B, t));
-    }
-
-    public static LerpNumber(a: number, b: number, percent: number): number {
-        return a + percent * (b - a);
-    }
-
-    static hexToRGB(hex: number, alpha: number): number[] {
-        var r = (hex & (0xff << 16)) >> 16;
-        var g = (hex & (0xff << 8)) >> 8;
-        var b = (hex & (0xff << 0)) >> 0;
-        return [r, g, b];
-    }
-    static RGBtoHex(red: number, green: number, blue: number): number {
-        return blue | (green << 8) | (red << 16);
-    }
-
-    public static RGBtoHexString(rgb: number): string {
-        let str = "#" + this.hex((rgb & (0xff << 16)) >> 16) + this.hex((rgb & (0xff << 8)) >> 8) + this.hex((rgb & (0xff << 0)) >> 0);
-        return str;
-    }
-
-    static hex(x: number): string {
-        var hexDigits = new Array
-            ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f");
-        return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
-    }
-
-    public static FromColor(c: number): LABColor {
-        var rgb = LABColor.hexToRGB(c, 0);
-        var r = LABColor.d3_rgb_xyz(rgb[0] * 255);
-        var g = LABColor.d3_rgb_xyz(rgb[1] * 255);
-        var b = LABColor.d3_rgb_xyz(rgb[2] * 255);
-
-        var x = LABColor.d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LABColor.d3_lab_X);
-        var y = LABColor.d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LABColor.d3_lab_Y);
-        var z = LABColor.d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LABColor.d3_lab_Z);
-        var lab = new LABColor(116 * y - 16, 500 * (x - y), 200 * (y - z));
-        return lab;
-    }
-
-    private static d3_lab_X: number = 0.950470;
-    private static d3_lab_Y: number = 1;
-    private static d3_lab_Z: number = 1.088830;
-
-    public static d3_lab_xyz(x: number): number {
-        return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
-    }
-
-    public static d3_xyz_rgb(r: number): number {
-        return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055));
-    }
-
-    public static d3_rgb_xyz(r: number): number {
-        return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
-    }
-
-    public static d3_xyz_lab(x: number): number {
-        return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
-    }
-
-    public static ToColor(lab: LABColor): number {
-        var y = (lab.L + 16) / 116;
-        var x = y + lab.A / 500;
-        var z = y - lab.B / 200;
-        x = LABColor.d3_lab_xyz(x) * LABColor.d3_lab_X;
-        y = LABColor.d3_lab_xyz(y) * LABColor.d3_lab_Y;
-        z = LABColor.d3_lab_xyz(z) * LABColor.d3_lab_Z;
-
-        return LABColor.RGBtoHex(
-            LABColor.d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z) / 255,
-            LABColor.d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z) / 255,
-            LABColor.d3_xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z) / 255);
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
deleted file mode 100644
index 5def5e704..000000000
--- a/src/client/northstar/utils/MathUtil.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-
-
-export class PIXIPoint {
-    public get x() { return this.coords[0]; }
-    public get y() { return this.coords[1]; }
-    public set x(value: number) { this.coords[0] = value; }
-    public set y(value: number) { this.coords[1] = value; }
-    public coords: number[] = [0, 0];
-    constructor(x: number, y: number) {
-        this.coords[0] = x;
-        this.coords[1] = y;
-    }
-}
-
-export class PIXIRectangle {
-    public x: number;
-    public y: number;
-    public width: number;
-    public height: number;
-    public get left() { return this.x; }
-    public get right() { return this.x + this.width; }
-    public get top() { return this.y; }
-    public get bottom() { return this.top + this.height; }
-    public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); }
-    constructor(x: number, y: number, width: number, height: number) {
-        this.x = x;
-        this.y = y;
-        this.width = width;
-        this.height = height;
-    }
-}
-
-export class MathUtil {
-
-    public static EPSILON: number = 0.001;
-
-    public static Sign(value: number): number {
-        return value >= 0 ? 1 : -1;
-    }
-
-    public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
-        if (inline) {
-            p1.x += p2.x;
-            p1.y += p2.y;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x + p2.x, p1.y + p2.y);
-        }
-    }
-
-    public static Perp(p1: PIXIPoint): PIXIPoint {
-        return new PIXIPoint(-p1.y, p1.x);
-    }
-
-    public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint {
-        if (inline) {
-            p1.x /= by;
-            p1.y /= by;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x / by, p1.y / by);
-        }
-    }
-
-    public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) {
-        if (inline) {
-            p1.x *= by;
-            p1.y *= by;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x * by, p1.y * by);
-        }
-    }
-
-    public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
-        if (inline) {
-            p1.x -= p2.x;
-            p1.y -= p2.y;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x - p2.x, p1.y - p2.y);
-        }
-    }
-
-    public static Area(rect: PIXIRectangle): number {
-        return rect.width * rect.height;
-    }
-
-    public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
-        // Return minimum distance between line segment vw and point p
-        const l2 = MathUtil.DistSquared(v, w);  // i.e. |w-v|^2 -  avoid a sqrt
-        if (l2 === 0.0) return MathUtil.Dist(p, v);   // v === w case
-        // Consider the line extending the segment, parameterized as v + t (w - v).
-        // We find projection of point p onto the line. 
-        // It falls where t = [(p-v) . (w-v)] / |w-v|^2
-        // We clamp t from [0,1] to handle points outside the segment vw.
-        const dot = MathUtil.Dot(
-            MathUtil.SubtractPoint(p, v),
-            MathUtil.SubtractPoint(w, v)) / l2;
-        const t = Math.max(0, Math.min(1, dot));
-        // Projection falls on the segment
-        const projection = MathUtil.AddPoint(v,
-            MathUtil.MultiplyConstant(
-                MathUtil.SubtractPoint(w, v), t));
-        return MathUtil.Dist(p, projection);
-    }
-
-    public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined {
-        const a1 = pe1.y - ps1.y;
-        const b1 = ps1.x - pe1.x;
-
-        const a2 = pe2.y - ps2.y;
-        const b2 = ps2.x - pe2.x;
-
-        const delta = a1 * b2 - a2 * b1;
-        if (delta === 0) {
-            return undefined;
-        }
-        const c2 = a2 * ps2.x + b2 * ps2.y;
-        const c1 = a1 * ps1.x + b1 * ps1.y;
-        const invdelta = 1 / delta;
-        return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta);
-    }
-
-    public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
-        if (p.x < rect.left - this.EPSILON) {
-            return false;
-        }
-        if (p.x > rect.right + this.EPSILON) {
-            return false;
-        }
-        if (p.y < rect.top - this.EPSILON) {
-            return false;
-        }
-        if (p.y > rect.bottom + this.EPSILON) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> {
-        const r1 = new PIXIPoint(rect.left, rect.top);
-        const r2 = new PIXIPoint(rect.right, rect.top);
-        const r3 = new PIXIPoint(rect.right, rect.bottom);
-        const r4 = new PIXIPoint(rect.left, rect.bottom);
-        const ret = new Array<PIXIPoint>();
-        const dist = this.Dist(lineFrom, lineTo);
-        let inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        return ret;
-    }
-
-    public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle {
-        const left = Math.max(rect1.x, rect2.x);
-        const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);
-        const top = Math.max(rect1.y, rect2.y);
-        const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);
-        return new PIXIRectangle(left, top, right - left, bottom - top);
-    }
-
-    public static Dist(p1: PIXIPoint, p2: PIXIPoint): number {
-        return Math.sqrt(MathUtil.DistSquared(p1, p2));
-    }
-
-    public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
-        return p1.x * p2.x + p1.y * p2.y;
-    }
-
-    public static Normalize(p1: PIXIPoint) {
-        const d = this.Length(p1);
-        return new PIXIPoint(p1.x / d, p1.y / d);
-    }
-
-    public static Length(p1: PIXIPoint): number {
-        return Math.sqrt(p1.x * p1.x + p1.y * p1.y);
-    }
-
-    public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
-        const a = p1.x - p2.x;
-        const b = p1.y - p2.y;
-        return (a * a + b * b);
-    }
-
-    public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
-        return !(r2.x > r1.x + r1.width ||
-            r2.x + r2.width < r1.x ||
-            r2.y > r1.y + r1.height ||
-            r2.y + r2.height < r1.y);
-    }
-
-    public static ArgMin(temp: number[]): number {
-        let index = 0;
-        let value = temp[0];
-        for (let i = 1; i < temp.length; i++) {
-            if (temp[i] < value) {
-                value = temp[i];
-                index = i;
-            }
-        }
-        return index;
-    }
-
-    public static ArgMax(temp: number[]): number {
-        let index = 0;
-        let value = temp[0];
-        for (let i = 1; i < temp.length; i++) {
-            if (temp[i] > value) {
-                value = temp[i];
-                index = i;
-            }
-        }
-        return index;
-    }
-
-    public static Combinations<T>(chars: T[]) {
-        const result = new Array<T>();
-        const f = (prefix: any, chars: any) => {
-            for (let i = 0; i < chars.length; i++) {
-                result.push(prefix.concat(chars[i]));
-                f(prefix.concat(chars[i]), chars.slice(i + 1));
-            }
-        };
-        f([], chars);
-        return result;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/PartialClass.ts b/src/client/northstar/utils/PartialClass.ts
deleted file mode 100644
index 2f20de96f..000000000
--- a/src/client/northstar/utils/PartialClass.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-
-export class PartialClass<T> {
-
-    constructor(data?: Partial<T>) {
-        Object.assign(this, data);
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
deleted file mode 100644
index a52890ed9..000000000
--- a/src/client/northstar/utils/SizeConverter.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { PIXIPoint } from "./MathUtil";
-import { VisualBinRange } from "../model/binRanges/VisualBinRange";
-import { Bin, DoubleValueAggregateResult, AggregateKey } from "../model/idea/idea";
-import { ModelHelpers } from "../model/ModelHelpers";
-import { observable, action, computed } from "mobx";
-
-export class SizeConverter {
-    public DataMins: Array<number> = new Array<number>(2);
-    public DataMaxs: Array<number> = new Array<number>(2);
-    public DataRanges: Array<number> = new Array<number>(2);
-    public MaxLabelSizes: Array<PIXIPoint> = new Array<PIXIPoint>(2);
-    public RenderDimension: number = 300;
-
-    @observable _leftOffset: number = 40;
-    @observable _rightOffset: number = 20;
-    @observable _topOffset: number = 20;
-    @observable _bottomOffset: number = 45;
-    @observable _labelAngle: number = 0;
-    @observable _isSmall: boolean = false;
-    @observable public Initialized = 0;
-
-    @action public SetIsSmall(isSmall: boolean) { this._isSmall = isSmall; }
-    @action public SetLabelAngle(angle: number) { this._labelAngle = angle; }
-    @computed public get IsSmall() { return this._isSmall; }
-    @computed public get LabelAngle() { return this._labelAngle; }
-    @computed public get LeftOffset() { return this.IsSmall ? 5 : this._leftOffset; }
-    @computed public get RightOffset() { return this.IsSmall ? 5 : !this._labelAngle ? this._bottomOffset : Math.max(this._rightOffset, Math.cos(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)); }
-    @computed public get TopOffset() { return this.IsSmall ? 5 : this._topOffset; }
-    @computed public get BottomOffset() { return this.IsSmall ? 25 : !this._labelAngle ? this._bottomOffset : Math.max(this._bottomOffset, Math.sin(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)) + 18; }
-
-    public SetVisualBinRanges(visualBinRanges: Array<VisualBinRange>) {
-        this.Initialized++;
-        var xLabels = visualBinRanges[0].GetLabels();
-        var yLabels = visualBinRanges[1].GetLabels();
-        var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
-        var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
-
-        var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
-        //xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
-        var metricsY = { width: 22 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
-        // yLabelStrings[0]!.slice(0, 20)); // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
-        this.MaxLabelSizes[0] = new PIXIPoint(metricsX.width, 12);// FontStyles.AxisLabel.fontSize as number);
-        this.MaxLabelSizes[1] = new PIXIPoint(metricsY.width, 12); // FontStyles.AxisLabel.fontSize as number);
-
-        this._leftOffset = Math.max(10, metricsY.width + 10 + 20);
-
-        this.DataMins[0] = xLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE);
-        this.DataMins[1] = yLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE);
-        this.DataMaxs[0] = xLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE);
-        this.DataMaxs[1] = yLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE);
-
-        this.DataRanges[0] = this.DataMaxs[0] - this.DataMins[0];
-        this.DataRanges[1] = this.DataMaxs[1] - this.DataMins[1];
-    }
-
-    public DataToScreenNormalizedRange(dataValue: number, normalization: number, axis: number, binBrushMaxAxis: number) {
-        var value = normalization !== 1 - axis || binBrushMaxAxis === 0 ? dataValue : (dataValue - 0) / (binBrushMaxAxis - 0) * this.DataRanges[axis];
-        var from = this.DataToScreenCoord(Math.min(0, value), axis);
-        var to = this.DataToScreenCoord(Math.max(0, value), axis);
-        return [from, value, to];
-    }
-
-    public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) {
-        var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
-        if (value && value.hasResult) {
-            return [this.DataToScreenCoord(value.result!, axis) - 5,
-            this.DataToScreenCoord(value.result!, axis) + 5];
-        }
-        return [undefined, undefined];
-    }
-
-    public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
-        var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]);
-        return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))];
-    }
-    public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
-        var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]);
-        return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))];
-    }
-
-    public DataToScreenX(x: number): number {
-        return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension;
-    }
-    public DataToScreenY(y: number, flip: boolean = true) {
-        var retY = ((y - this.DataMins[1]) / this.DataRanges[1]) * this.RenderDimension;
-        return flip ? (this.RenderDimension) - retY : retY;
-    }
-    public DataToScreenCoord(v: number, axis: number) {
-        if (axis === 0) {
-            return this.DataToScreenX(v);
-        }
-        return this.DataToScreenY(v);
-    }
-    public DataToScreenRange(minVal: number, maxVal: number, axis: number) {
-        let xFrom = this.DataToScreenX(axis === 0 ? minVal : this.DataMins[0]);
-        let xTo = this.DataToScreenX(axis === 0 ? maxVal : this.DataMaxs[0]);
-        let yFrom = this.DataToScreenY(axis === 1 ? minVal : this.DataMins[1]);
-        let yTo = this.DataToScreenY(axis === 1 ? maxVal : this.DataMaxs[1]);
-        return { xFrom, yFrom, xTo, yTo };
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/utils/StyleContants.ts b/src/client/northstar/utils/StyleContants.ts
deleted file mode 100644
index e9b6e0297..000000000
--- a/src/client/northstar/utils/StyleContants.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { PIXIPoint } from "./MathUtil";
-
-export class StyleConstants {
-
-    static DEFAULT_FONT: string = "Roboto Condensed";
-
-    static MENU_SUBMENU_WIDTH: number = 85;
-    static MENU_SUBMENU_HEIGHT: number = 400;
-    static MENU_BOX_SIZE: PIXIPoint = new PIXIPoint(80, 35);
-    static MENU_BOX_PADDING: number = 10;
-
-    static OPERATOR_MENU_LARGE: number = 35;
-    static OPERATOR_MENU_SMALL: number = 25;
-    static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd];
-    static GAP: number = 3;
-
-    static BACKGROUND_COLOR: number = 0xF3F3F3;
-    static TOOL_TIP_BACKGROUND_COLOR: number = 0xffffff;
-    static LIGHT_TEXT_COLOR: number = 0xffffff;
-    static LIGHT_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.LIGHT_TEXT_COLOR);
-    static DARK_TEXT_COLOR: number = 0x282828;
-    static HIGHLIGHT_TEXT_COLOR: number = 0xffcc00;
-    static FPS_TEXT_COLOR: number = StyleConstants.DARK_TEXT_COLOR;
-    static CORRELATION_LABEL_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR);
-    static LOADING_SCREEN_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR);
-    static ERROR_COLOR: number = 0x540E25;
-    static WARNING_COLOR: number = 0xE58F24;
-    static LOWER_THAN_NAIVE_COLOR: number = 0xee0000;
-    static HIGHLIGHT_COLOR: number = 0x82A8D9;
-    static HIGHLIGHT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR);
-    static OPERATOR_BACKGROUND_COLOR: number = 0x282828;
-    static LOADING_ANIMATION_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR;
-    static MENU_COLOR: number = 0x282828;
-    static MENU_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR;
-    static MENU_SELECTED_COLOR: number = StyleConstants.HIGHLIGHT_COLOR;
-    static MENU_SELECTED_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR;
-    static BRUSH_COLOR: number = 0xff0000;
-    static DROP_ACCEPT_COLOR: number = StyleConstants.HIGHLIGHT_COLOR;
-    static SELECTED_COLOR: number = 0xffffff;
-    static SELECTED_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.SELECTED_COLOR);
-    static PROGRESS_BACKGROUND_COLOR: number = 0x595959;
-    static GRID_LINES_COLOR: number = 0x3D3D3D;
-    static GRID_LINES_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.GRID_LINES_COLOR);
-
-    static MAX_CHAR_FOR_HISTOGRAM_LABELS: number = 20;
-
-    static OVERLAP_COLOR: number = 0x0000ff;//0x540E25;
-    static BRUSH_COLORS: Array<number> = new Array<number>(
-        0xFFDA7E, 0xFE8F65, 0xDA5655, 0x8F2240
-    );
-
-    static MIN_VALUE_COLOR: number = 0x373d43; //32343d, 373d43,  3b4648
-    static MARGIN_BARS_COLOR: number = 0xffffff;
-    static MARGIN_BARS_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.MARGIN_BARS_COLOR);
-
-    static HISTOGRAM_WIDTH: number = 200;
-    static HISTOGRAM_HEIGHT: number = 150;
-    static PREDICTOR_WIDTH: number = 150;
-    static PREDICTOR_HEIGHT: number = 100;
-    static RAWDATA_WIDTH: number = 150;
-    static RAWDATA_HEIGHT: number = 100;
-    static FREQUENT_ITEM_WIDTH: number = 180;
-    static FREQUENT_ITEM_HEIGHT: number = 100;
-    static CORRELATION_WIDTH: number = 555;
-    static CORRELATION_HEIGHT: number = 390;
-    static PROBLEM_FINDER_WIDTH: number = 450;
-    static PROBLEM_FINDER_HEIGHT: number = 150;
-    static PIPELINE_OPERATOR_WIDTH: number = 300;
-    static PIPELINE_OPERATOR_HEIGHT: number = 120;
-    static SLICE_WIDTH: number = 150;
-    static SLICE_HEIGHT: number = 45;
-    static BORDER_MENU_ITEM_WIDTH: number = 50;
-    static BORDER_MENU_ITEM_HEIGHT: number = 30;
-
-
-    static SLICE_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR);
-    static SLICE_EMPTY_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR;
-    static SLICE_OCCUPIED_COLOR: number = 0xffffff;
-    static SLICE_OCCUPIED_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR);
-    static SLICE_HOVER_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR);
-    static SLICE_HOVER_COLOR: number = 0xffffff;
-
-    static HexToHexString(hex: number): string {
-        if (hex === undefined) {
-            return "#000000";
-        }
-        var s = hex.toString(16);
-        while (s.length < 6) {
-            s = "0" + s;
-        }
-        return "#" + s;
-    }
-
-
-} 
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
deleted file mode 100644
index d071dec62..000000000
--- a/src/client/northstar/utils/Utils.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { IBaseBrushable } from '../core/brusher/IBaseBrushable';
-import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer';
-import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider';
-import { AggregateFunction } from '../model/idea/idea';
-
-export class Utils {
-
-    public static EqualityHelper(a: Object, b: Object): boolean {
-        if (a === b) return true;
-        if (a === undefined && b !== undefined) return false;
-        if (a === null && b !== null) return false;
-        if (b === undefined && a !== undefined) return false;
-        if (b === null && a !== null) return false;
-        if ((<any>a).constructor.name !== (<any>b).constructor.name) return false;
-        return true;
-    }
-
-    public static LowercaseFirstLetter(str: string) {
-        return str.charAt(0).toUpperCase() + str.slice(1);
-    }
-
-    //
-    // this Type Guard tests if dropTarget is an IDropTarget.  If it is, it coerces the compiler
-    // to treat the dropTarget parameter as an IDropTarget *ouside* this function scope (ie, in
-    // the scope of where this function is called from).
-    //
-
-    public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> {
-        let typed = <IBaseBrushable<T>>obj;
-        return typed !== null && typed.BrusherModels !== undefined;
-    }
-
-    public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider {
-        let typed = <IBaseFilterProvider>obj;
-        return typed !== null && typed.FilterModels !== undefined;
-    }
-
-    public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer {
-        let typed = <IBaseFilterConsumer>obj;
-        return typed !== null && typed.FilterOperand !== undefined;
-    }
-
-    public static EncodeQueryData(data: any): string {
-        const ret = [];
-        for (let d in data) {
-            ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
-        }
-        return ret.join("&");
-    }
-
-    public static ToVegaAggregationString(agg: AggregateFunction): string {
-        if (agg === AggregateFunction.Avg) {
-            return "average";
-        }
-        else if (agg === AggregateFunction.Count) {
-            return "count";
-        }
-        else {
-            return "";
-        }
-    }
-
-    public static GetQueryVariable(variable: string) {
-        let query = window.location.search.substring(1);
-        let vars = query.split("&");
-        for (const variable of vars) {
-            let pair = variable.split("=");
-            if (decodeURIComponent(pair[0]) === variable) {
-                return decodeURIComponent(pair[1]);
-            }
-        }
-        return undefined;
-    }
-}
-
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 569c1ef6d..b3295ece0 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -10,7 +10,6 @@ import { CollectionViewType } from "../views/collections/CollectionView";
 import { Cast, CastCtor } from "../../new_fields/Types";
 import { listSpec } from "../../new_fields/Schema";
 import { AudioField, ImageField } from "../../new_fields/URLField";
-import { HistogramField } from "../northstar/dash-fields/HistogramField";
 import { Utils } from "../../Utils";
 import { RichTextField } from "../../new_fields/RichTextField";
 import { DictationOverlay } from "../views/DictationOverlay";
@@ -282,9 +281,8 @@ export namespace DictationManager {
             [DocumentType.COL, listSpec(Doc)],
             [DocumentType.AUDIO, AudioField],
             [DocumentType.IMG, ImageField],
-            [DocumentType.HIST, HistogramField],
             [DocumentType.IMPORT, listSpec(Doc)],
-            [DocumentType.TEXT, "string"]
+            [DocumentType.RTF, "string"]
         ]);
 
         const tryCast = (view: DocumentView, type: DocumentType) => {
@@ -377,7 +375,7 @@ export namespace DictationManager {
             {
                 expression: /view as (freeform|stacking|masonry|schema|tree)/g,
                 action: (target: DocumentView, matches: RegExpExecArray) => {
-                    const mode = CollectionViewType.valueOf(matches[1]);
+                    const mode = matches[1];
                     mode && (target.props.Document._viewType = mode);
                 },
                 restrictTo: [DocumentType.COL]
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index d96257ca1..7b38fdac2 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -52,7 +52,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
         // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
         if (!doc.onDragStart && !doc.isButtonBar) {
             const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
-            if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) {
+            if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.RTF || layoutDoc.type === DocumentType.IMG) {
                 !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
             } else {
                 (layoutDoc.layout instanceof Doc) && !data.userDropAction;
diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts
new file mode 100644
index 000000000..cacb72a57
--- /dev/null
+++ b/src/client/util/KeyCodes.ts
@@ -0,0 +1,136 @@
+/**
+ * Class contains the keycodes for keys on your keyboard.
+ *
+ * Useful for auto completion:
+ *
+ * ```
+ * switch (event.key)
+ * {
+ *      case KeyCode.UP:
+ *      {
+ *      	// Up key pressed
+ *       	break;
+ *      }
+ *      case KeyCode.DOWN:
+ *      {
+ *      	// Down key pressed
+ *       	break;
+ *      }
+ *      case KeyCode.LEFT:
+ *      {
+ *      	// Left key pressed
+ *       	break;
+ *      }
+ *      case KeyCode.RIGHT:
+ *      {
+ *      	// Right key pressed
+ *       	break;
+ *      }
+ *      default:
+ *      {
+ *      	// ignore
+ *      	break;
+ *      }
+ * }
+ * ```
+ */
+export class KeyCodes {
+    public static TAB: number = 9;
+    public static CAPS_LOCK: number = 20;
+    public static SHIFT: number = 16;
+    public static CONTROL: number = 17;
+    public static SPACE: number = 32;
+    public static DOWN: number = 40;
+    public static UP: number = 38;
+    public static LEFT: number = 37;
+    public static RIGHT: number = 39;
+    public static ESCAPE: number = 27;
+    public static F1: number = 112;
+    public static F2: number = 113;
+    public static F3: number = 114;
+    public static F4: number = 115;
+    public static F5: number = 116;
+    public static F6: number = 117;
+    public static F7: number = 118;
+    public static F8: number = 119;
+    public static F9: number = 120;
+    public static F10: number = 121;
+    public static F11: number = 122;
+    public static F12: number = 123;
+    public static INSERT: number = 45;
+    public static HOME: number = 36;
+    public static PAGE_UP: number = 33;
+    public static PAGE_DOWN: number = 34;
+    public static DELETE: number = 46;
+    public static END: number = 35;
+    public static ENTER: number = 13;
+    public static BACKSPACE: number = 8;
+    public static NUMPAD_0: number = 96;
+    public static NUMPAD_1: number = 97;
+    public static NUMPAD_2: number = 98;
+    public static NUMPAD_3: number = 99;
+    public static NUMPAD_4: number = 100;
+    public static NUMPAD_5: number = 101;
+    public static NUMPAD_6: number = 102;
+    public static NUMPAD_7: number = 103;
+    public static NUMPAD_8: number = 104;
+    public static NUMPAD_9: number = 105;
+    public static NUMPAD_DIVIDE: number = 111;
+    public static NUMPAD_ADD: number = 107;
+    public static NUMPAD_ENTER: number = 13;
+    public static NUMPAD_DECIMAL: number = 110;
+    public static NUMPAD_SUBTRACT: number = 109;
+    public static NUMPAD_MULTIPLY: number = 106;
+    public static SEMICOLON: number = 186;
+    public static EQUAL: number = 187;
+    public static COMMA: number = 188;
+    public static MINUS: number = 189;
+    public static PERIOD: number = 190;
+    public static SLASH: number = 191;
+    public static BACKQUOTE: number = 192;
+    public static LEFTBRACKET: number = 219;
+    public static BACKSLASH: number = 220;
+    public static RIGHTBRACKET: number = 221;
+    public static QUOTE: number = 222;
+    public static ALT: number = 18;
+    public static COMMAND: number = 15;
+    public static NUMPAD: number = 21;
+    public static A: number = 65;
+    public static B: number = 66;
+    public static C: number = 67;
+    public static D: number = 68;
+    public static E: number = 69;
+    public static F: number = 70;
+    public static G: number = 71;
+    public static H: number = 72;
+    public static I: number = 73;
+    public static J: number = 74;
+    public static K: number = 75;
+    public static L: number = 76;
+    public static M: number = 77;
+    public static N: number = 78;
+    public static O: number = 79;
+    public static P: number = 80;
+    public static Q: number = 81;
+    public static R: number = 82;
+    public static S: number = 83;
+    public static T: number = 84;
+    public static U: number = 85;
+    public static V: number = 86;
+    public static W: number = 87;
+    public static X: number = 88;
+    public static Y: number = 89;
+    public static Z: number = 90;
+    public static NUM_0: number = 48;
+    public static NUM_1: number = 49;
+    public static NUM_2: number = 50;
+    public static NUM_3: number = 51;
+    public static NUM_4: number = 52;
+    public static NUM_5: number = 53;
+    public static NUM_6: number = 54;
+    public static NUM_7: number = 55;
+    public static NUM_8: number = 56;
+    public static NUM_9: number = 57;
+    public static SUBSTRACT: number = 189;
+    public static ADD: number = 187;
+}
\ No newline at end of file
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 97f6b79fb..08aec3724 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -195,7 +195,6 @@ interface DocumentOptions { }
 declare const Docs: {
     ImageDocument(url: string, options?: DocumentOptions): Doc;
     VideoDocument(url: string, options?: DocumentOptions): Doc;
-    // HistogramDocument(url:string, options?:DocumentOptions);
     TextDocument(options?: DocumentOptions): Doc;
     PdfDocument(url: string, options?: DocumentOptions): Doc;
     WebDocument(url: string, options?: DocumentOptions): Doc;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 21eec66be..0c8ce28c3 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -9,7 +9,7 @@ import { PositionDocument } from '../../new_fields/documentSchemas';
 import { InteractionUtils } from '../util/InteractionUtils';
 
 
-///  DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, ButtonBox)
+///  DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, LabelBox)
 interface DocComponentProps {
     Document: Doc;
     LayoutDoc?: () => Opt<Doc>;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e313b117f..bd72385ef 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -76,7 +76,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
             if (documentView.props.Document.type === DocumentType.LINK) {
-                const docuBox = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont");
+                const docuBox = documentView.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
                 if (docuBox.length) {
                     const rect = docuBox[0].getBoundingClientRect();
                     sptX = rect.left;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bef92f0fd..7c9f47fe4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -275,7 +275,7 @@ export class MainView extends React.Component {
     defaultBackgroundColors = (doc: Doc) => {
         if (this.darkScheme) {
             switch (doc.type) {
-                case DocumentType.TEXT || DocumentType.BUTTON: return "#2d2d2d";
+                case DocumentType.RTF || DocumentType.LABEL: return "#2d2d2d";
                 case DocumentType.LINK:
                 case DocumentType.COL: {
                     if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
@@ -284,8 +284,8 @@ export class MainView extends React.Component {
             }
         } else {
             switch (doc.type) {
-                case DocumentType.TEXT: return "#f1efeb";
-                case DocumentType.BUTTON: return "lightgray";
+                case DocumentType.RTF: return "#f1efeb";
+                case DocumentType.LABEL: return "lightgray";
                 case DocumentType.LINK:
                 case DocumentType.COL: {
                     if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray";
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 1e81bb80b..0352ddaca 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -50,7 +50,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
     }
 
     onBlur = () => {
-        this.overlayDisposer && this.overlayDisposer();
+        this.overlayDisposer?.();
     }
 
     render() {
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
index 4790a2ad7..799fa9d85 100644
--- a/src/client/views/SearchDocBox.tsx
+++ b/src/client/views/SearchDocBox.tsx
@@ -394,7 +394,7 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
 
     render() {
         const isEditing = this.editingMetadata;
-        return (
+        return !this.content ? (null) : (
             <div style={{ pointerEvents: "all" }}>
                 <ContentFittingDocumentView {...this.props}
                     Document={this.content}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index ae71c54f7..82204ca7b 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -4,8 +4,9 @@ import { observer } from "mobx-react";
 import { CellInfo } from "react-table";
 import "react-table/react-table.css";
 import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
+import { KeyCodes } from "../../util/KeyCodes";
 import { SetupDrag, DragManager } from "../../util/DragManager";
 import { CompileScript } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
@@ -21,9 +22,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faExpand } from '@fortawesome/free-solid-svg-icons';
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { KeyCodes } from "../../northstar/utils/KeyCodes";
 import { undoBatch } from "../../util/UndoManager";
-import { List } from "lodash";
 
 library.add(faExpand);
 
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index e835811c9..57be77fdd 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -14,7 +14,6 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { Gateway } from "../../northstar/manager/Gateway";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
@@ -673,27 +672,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         }
     }
 
-    @action
-    makeDB = async () => {
-        let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
-        csv = csv.substr(0, csv.length - 1) + "\n";
-        const self = this;
-        this.childDocs.map(doc => {
-            csv += self.columns.reduce((val, col) => val + (doc[col.heading] ? doc[col.heading]!.toString() : "0") + ",", "");
-            csv = csv.substr(0, csv.length - 1) + "\n";
-        });
-        csv.substring(0, csv.length - 1);
-        const dbName = StrCast(this.props.Document.title);
-        const res = await Gateway.Instance.PostSchema(csv, dbName);
-        if (self.props.CollectionView && self.props.CollectionView.props.addDocument) {
-            const schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
-            if (schemaDoc) {
-                //self.props.CollectionView.props.addDocument(schemaDoc, false);
-                self.props.Document.schemaDoc = schemaDoc;
-            }
-        }
-    }
-
     getField = (row: number, col?: number) => {
         const docs = this.childDocs;
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b066f2be3..da53888fc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -343,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
-                            Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0)
+                            Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
                         }
                     }));
                     this.observer.observe(ref);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 746b2e174..41f4fb3f0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -99,7 +99,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
-        rootSelected = (outsideReaction: boolean) => {
+        rootSelected = (outsideReaction?: boolean) => {
             return this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected(outsideReaction) : false);
         }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c7ab66c9f..5819c829f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -52,39 +52,19 @@ const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
 
 export enum CollectionViewType {
-    Invalid,
-    Freeform,
-    Schema,
-    Docking,
-    Tree,
-    Stacking,
-    Masonry,
-    Multicolumn,
-    Multirow,
-    Time,
-    Carousel,
-    Linear,
-    Staff
-}
-
-export namespace CollectionViewType {
-    const stringMapping = new Map<string, CollectionViewType>([
-        ["invalid", CollectionViewType.Invalid],
-        ["freeform", CollectionViewType.Freeform],
-        ["schema", CollectionViewType.Schema],
-        ["docking", CollectionViewType.Docking],
-        ["tree", CollectionViewType.Tree],
-        ["stacking", CollectionViewType.Stacking],
-        ["masonry", CollectionViewType.Masonry],
-        ["multicolumn", CollectionViewType.Multicolumn],
-        ["multirow", CollectionViewType.Multirow],
-        ["time", CollectionViewType.Time],
-        ["carousel", CollectionViewType.Carousel],
-        ["linear", CollectionViewType.Linear],
-    ]);
-
-    export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
-    export const stringFor = (value: number) => Array.from(stringMapping.entries()).find(entry => entry[1] === value)?.[0];
+    Invalid = "invalid",
+    Freeform = "freeform",
+    Schema = "schema",
+    Docking = "docking",
+    Tree = 'tree',
+    Stacking = "stacking",
+    Masonry = "masonry",
+    Multicolumn = "multicolumn",
+    Multirow = "multirow",
+    Time = "time",
+    Carousel = "carousel",
+    Linear = "linear",
+    Staff = "staff",
 }
 
 export interface CollectionRenderProps {
@@ -110,7 +90,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     get collectionViewType(): CollectionViewType | undefined {
-        const viewField = NumCast(this.props.Document._viewType);
+        const viewField = StrCast(this.props.Document._viewType);
         if (CollectionView._safeMode) {
             if (viewField === CollectionViewType.Freeform) {
                 return CollectionViewType.Tree;
@@ -119,7 +99,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 return CollectionViewType.Freeform;
             }
         }
-        return viewField;
+        return viewField as any as CollectionViewType;
     }
 
     active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
@@ -394,7 +374,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
                 newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
             }
-            Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
+            newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
         }
     }
 
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 9bade1c82..7741f7d42 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -18,7 +18,6 @@ import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
 import * as Autosuggest from 'react-autosuggest';
 import KeyRestrictionRow from "./KeyRestrictionRow";
-import { ObjectField } from "../../../new_fields/ObjectField";
 const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
@@ -349,11 +348,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     dragViewDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e, down, delta) => {
-            const vtype = NumCast(this.props.CollectionView.props.Document._viewType) as CollectionViewType;
+            const vtype = this.props.CollectionView.collectionViewType;
             const c = {
-                params: ["target"], title: CollectionViewType.stringFor(vtype),
+                params: ["target"], title: vtype,
                 script: `this.target._viewType = ${NumCast(this.props.CollectionView.props.Document._viewType)}`,
-                immediate: (source: Doc[]) => this.target = Doc.getTemplateDoc(source?.[0]),
+                immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
                 initialize: emptyFunction,
             };
             DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index afe269ec3..2f0132fec 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -54,7 +54,7 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
     getOnClick({ col, target }: { col: Doc, target: Doc }) {
         return () => {
             col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-            if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+            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;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index a33146388..09fc5148e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -26,8 +26,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             action(() => {
                 setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
                 setTimeout(action(() => this._opacity = 0.05), 750); // this will unhighlight the link line.
-                const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
-                const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+                const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+                const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
                 const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
                 const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
                 const a = adiv.getBoundingClientRect();
@@ -43,7 +43,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
                 const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
 
-                // really hacky stuff to make the DocuLinkBox display where we want it to:
+                // really hacky stuff to make the LinkAnchorBox display where we want it to:
                 //   if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
                 //   otherwise, we just use the computed nearest point on the document boundary to the target Document
                 const targetAhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]);
@@ -81,8 +81,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
     }
 
     render() {
-        const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
-        const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+        const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+        const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
         const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
         const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
         const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 49ca024a2..d12f93f15 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -43,60 +43,4 @@ export class CollectionFreeFormLinksView extends React.Component {
             {this.props.children}
         </div>;
     }
-    // _brushReactionDisposer?: IReactionDisposer;
-    // componentDidMount() {
-    //     this._brushReactionDisposer = reaction(
-    //         () => {
-    //             let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
-    //             return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
-    //         },
-    //         () => {
-    //             let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
-    //             let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
-    //             views.forEach((dstDoc, i) => {
-    //                 views.forEach((srcDoc, j) => {
-    //                     let dstTarg = dstDoc;
-    //                     let srcTarg = srcDoc;
-    //                     let x1 = NumCast(srcDoc.x);
-    //                     let x2 = NumCast(dstDoc.x);
-    //                     let x1w = NumCast(srcDoc.width, -1);
-    //                     let x2w = NumCast(dstDoc.width, -1);
-    //                     if (x1w < 0 || x2w < 0 || i === j) { }
-    //                     else {
-    //                         let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
-    //                             let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
-    //                             return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
-    //                         });
-    //                         let brushAction = (field: (Doc | Promise<Doc>)[]) => {
-    //                             let found = findBrush(field);
-    //                             if (found !== -1) {
-    //                                 field.splice(found, 1);
-    //                             }
-    //                         };
-    //                         if (Math.abs(x1 + x1w - x2) < 20) {
-    //                             let linkDoc: Doc = new Doc();
-    //                             linkDoc.title = "Histogram Brush";
-    //                             linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
-    //                             linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
-
-    //                             brushAction = (field: (Doc | Promise<Doc>)[]) => {
-    //                                 if (findBrush(field) === -1) {
-    //                                     field.push(linkDoc);
-    //                                 }
-    //                             };
-    //                         }
-    //                         if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>();
-    //                         if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>();
-    //                         let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
-    //                         let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
-    //                         brushAction(dstBrushDocs);
-    //                         brushAction(srcBrushDocs);
-    //                     }
-    //                 });
-    //             });
-    //         });
-    // }
-    // componentWillUnmount() {
-    //     this._brushReactionDisposer?.();
-    // }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index fb16b8365..53b54d7e4 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -88,7 +88,7 @@
                     opacity:0.9;
                     background-color: transparent;
                     box-shadow: black 2px 2px 1px;
-                    .docuLinkBox-cont {
+                    .linkAnchorBox-cont {
                         position: relative !important;
                         height: 100%  !important;
                         width: 100% !important;
@@ -103,7 +103,7 @@
                     box-shadow: black 1px 1px 1px;
                     margin-left: -1;
                     margin-top: -2;
-                    .docuLinkBox-cont {
+                    .linkAnchorBox-cont {
                         position: relative !important;
                         height: 100%  !important;
                         width: 100% !important;
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss
deleted file mode 100644
index 293af289d..000000000
--- a/src/client/views/nodes/ButtonBox.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-.buttonBox-outerDiv {
-    width: 100%;
-    height: 100%;
-    pointer-events: all;
-    border-radius: inherit;
-    display: flex;
-    flex-direction: column;
-}
-
-.buttonBox-mainButton {
-    width: 100%;
-    height: 100%;
-    border-radius: inherit;
-    letter-spacing: 2px;
-    text-transform: uppercase;
-    overflow: hidden;
-    display:flex;
-}
-
-.buttonBox-mainButtonCenter {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    display: inline;
-    align-items: center;
-    margin: auto;
-}
-
-.buttonBox-params {
-    display: flex;
-    flex-direction: row;
-}
-
-.buttonBox-missingParam {
-    width: 100%;
-    background: lightgray;
-    border: dimGray solid 1px;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
deleted file mode 100644
index 1b70ff824..000000000
--- a/src/client/views/nodes/ButtonBox.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-regular-svg-icons';
-import { action, computed } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
-import { List } from '../../../new_fields/List';
-import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
-import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types';
-import { DragManager } from '../../util/DragManager';
-import { undoBatch } from '../../util/UndoManager';
-import { DocComponent } from '../DocComponent';
-import './ButtonBox.scss';
-import { FieldView, FieldViewProps } from './FieldView';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ContextMenu } from '../ContextMenu';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-
-
-library.add(faEdit as any);
-
-const ButtonSchema = createSchema({
-    onClick: ScriptField,
-    buttonParams: listSpec("string"),
-    text: "string"
-});
-
-type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>;
-const ButtonDocument = makeInterface(ButtonSchema, documentSchema);
-
-@observer
-export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); }
-    private dropDisposer?: DragManager.DragDropDisposer;
-
-    @computed get dataDoc() {
-        return this.props.DataDoc &&
-            (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
-                this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
-    }
-
-
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        this.dropDisposer?.();
-        if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
-        }
-    }
-
-    specificContextMenu = (e: React.MouseEvent): void => {
-        const funcs: ContextMenuProps[] = [];
-        funcs.push({
-            description: "Clear Script Params", event: () => {
-                const params = FieldValue(this.Document.buttonParams);
-                params?.map(p => this.props.Document[p] = undefined);
-            }, icon: "trash"
-        });
-
-        ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
-    }
-
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        const docDragData = de.complete.docDragData;
-        const params = this.Document.buttonParams;
-        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
-        if (docDragData && missingParams?.includes((e.target as any).textContent)) {
-            this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
-                d.onDragStart ? docDragData.draggedDocuments[i] : d));
-            e.stopPropagation();
-        }
-    }
-    // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
-    render() {
-        const params = this.Document.buttonParams;
-        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
-        params?.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... 
-        return (
-            <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
-                style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
-                <div className="buttonBox-mainButton" style={{
-                    background: this.Document.backgroundColor, color: this.Document.color || "inherit",
-                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: (this.Document.textTransform as any) || ""
-                }} >
-                    <div className="buttonBox-mainButtonCenter">
-                        {(this.Document.text || this.Document.title)}
-                    </div>
-                </div>
-                <div className="buttonBox-params" >
-                    {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="buttonBox-missingParam">{m}</div>)}
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocuLinkBox.scss b/src/client/views/nodes/DocuLinkBox.scss
deleted file mode 100644
index f2c203548..000000000
--- a/src/client/views/nodes/DocuLinkBox.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-.docuLinkBox-cont, .docuLinkBox-cont-small {
-    cursor: default;
-    position: absolute;
-    width: 15;
-    height: 15;
-    border-radius: 20px;
-    pointer-events: all;
-    user-select: none;
-
-    .docuLinkBox-linkCloser {
-        position: absolute;
-        width: 18;
-        height: 18;
-        background: rgb(219, 21, 21);
-        top: -1px;
-        left: -1px;
-        border-radius: 5px;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        padding-left: 2px;
-        padding-top: 1px;
-    }
-}
-
-.docuLinkBox-cont-small {
-    width:5px;
-    height:5px;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
deleted file mode 100644
index 31ce58079..000000000
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
-import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils, setupMoveUpEvents } from '../../../Utils';
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
-import { DocComponent } from "../DocComponent";
-import "./DocuLinkBox.scss";
-import { FieldView, FieldViewProps } from "./FieldView";
-import React = require("react");
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ContextMenu } from "../ContextMenu";
-import { LinkEditor } from "../linking/LinkEditor";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { SelectionManager } from "../../util/SelectionManager";
-import { TraceMobx } from "../../../new_fields/util";
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-type DocLinkSchema = makeInterface<[typeof documentSchema]>;
-const DocLinkDocument = makeInterface(documentSchema);
-
-@observer
-export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(DocLinkDocument) {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey); }
-    _doubleTap = false;
-    _lastTap: number = 0;
-    _ref = React.createRef<HTMLDivElement>();
-    _isOpen = false;
-    _timeout: NodeJS.Timeout | undefined;
-    @observable _x = 0;
-    @observable _y = 0;
-    @observable _selected = false;
-    @observable _editing = false;
-    @observable _forceOpen = false;
-
-    onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onPointerMove, () => { }, this.onClick);
-    }
-    onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
-        const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
-        if (!this._isOpen && cdiv) {
-            const bounds = cdiv.getBoundingClientRect();
-            const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
-            const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
-            const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
-            if (separation > 100) {
-                const dragData = new DragManager.DocumentDragData([this.props.Document]);
-                dragData.dropAction = "alias";
-                dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isButton"];
-                DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
-                return true;
-            } else if (dragdist > separation) {
-                this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
-                this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
-            }
-        }
-        return false;
-    });
-    @action
-    onClick = (e: PointerEvent) => {
-        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0);
-        this._lastTap = Date.now();
-        if ((e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) {
-            this.props.select(false);
-        }
-        if (!this._doubleTap) {
-            const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack!  need a better prop for passing the anchor's container 
-            this._editing = true;
-            anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
-            if (anchorContainerDoc && !this.props.Document.onClick && !this._isOpen) {
-                this._timeout = setTimeout(action(() => {
-                    DocumentManager.Instance.FollowLink(this.props.Document, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
-                    this._editing = false;
-                }), 300 - (Date.now() - this._lastTap));
-            }
-        } else {
-            this._timeout && clearTimeout(this._timeout);
-            this._timeout = undefined;
-        }
-    }
-
-    openLinkDocOnRight = (e: React.MouseEvent) => {
-        this.props.addDocTab(this.props.Document, "onRight");
-    }
-    openLinkTargetOnRight = (e: React.MouseEvent) => {
-        const alias = Doc.MakeAlias(Cast(this.props.Document[this.props.fieldKey], Doc, null));
-        alias.isButton = undefined;
-        alias.isBackground = undefined;
-        alias.layoutKey = "layout";
-        this.props.addDocTab(alias, "onRight");
-    }
-    @action
-    openLinkEditor = action((e: React.MouseEvent) => {
-        SelectionManager.DeselectAll();
-        this._editing = this._forceOpen = true;
-    });
-
-    specificContextMenu = (e: React.MouseEvent): void => {
-        const funcs: ContextMenuProps[] = [];
-        funcs.push({ description: "Open Link Target on Right", event: () => this.openLinkTargetOnRight(e), icon: "eye" });
-        funcs.push({ description: "Open Link on Right", event: () => this.openLinkDocOnRight(e), icon: "eye" });
-        funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
-
-        ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
-    }
-
-    render() {
-        TraceMobx();
-        const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0;
-        const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0;
-        const c = StrCast(this.props.Document.backgroundColor, "lightblue");
-        const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
-        const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
-
-        const timecode = this.props.Document[anchor + "Timecode"];
-        const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
-        const flyout = (
-            <div className="docuLinkBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
-                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
-                {!this._forceOpen ? (null) : <div className="docuLinkBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
-                    <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
-                </div>}
-            </div>
-        );
-        const small = this.props.PanelWidth() <= 1;
-        return <div className={`docuLinkBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} title={targetTitle} onContextMenu={this.specificContextMenu}
-            ref={this._ref} style={{
-                background: c,
-                left: !small ? `calc(${x}% - 7.5px)` : undefined,
-                top: !small ? `calc(${y}% - 7.5px)` : undefined,
-                transform: `scale(${anchorScale / this.props.ContentScaling()})`
-            }} >
-            {!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" >
-                        <FontAwesomeIcon icon={"eye"} size={"lg"} />
-                    </span>
-                </Flyout>}
-        </div>;
-    }
-}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 0e2685d41..4f2b3b656 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -18,12 +18,12 @@ import { TraceMobx } from "../../../new_fields/util";
 import { DocumentView } from "./DocumentView";
 import { Docs } from "../../documents/Documents";
 
-type DocBoxSchema = makeInterface<[typeof documentSchema]>;
-const DocBoxDocument = makeInterface(documentSchema);
+type DocHolderBoxSchema = makeInterface<[typeof documentSchema]>;
+const DocHolderBoxDocument = makeInterface(documentSchema);
 
 @observer
-export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocumentBox, fieldKey); }
+export class DocHolderBox extends DocAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
     _prevSelectionDisposer: IReactionDisposer | undefined;
     _selections: Doc[] = [];
     _curSelection = -1;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index dc71ba280..7522af3a3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -3,7 +3,6 @@ import { observer } from "mobx-react";
 import { Doc, Opt } from "../../../new_fields/Doc";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Without } from "../../../Utils";
-import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -11,10 +10,11 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionView } from "../collections/CollectionView";
 import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
 import { AudioBox } from "./AudioBox";
-import { ButtonBox } from "./ButtonBox";
+import { LabelBox } from "./LabelBox";
 import { SliderBox } from "./SliderBox";
 import { LinkBox } from "./LinkBox";
-import { DocumentBox } from "./DocumentBox";
+import { ScriptingBox } from "./ScriptingBox";
+import { DocHolderBox } from "./DocumentBox";
 import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FontIconBox } from "./FontIconBox";
@@ -27,7 +27,7 @@ import { PresBox } from "./PresBox";
 import { QueryBox } from "./QueryBox";
 import { ColorBox } from "./ColorBox";
 import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
-import { DocuLinkBox } from "./DocuLinkBox";
+import { LinkAnchorBox } from "./LinkAnchorBox";
 import { PresElementBox } from "../presentationview/PresElementBox";
 import { ScreenshotBox } from "./ScreenshotBox";
 import { VideoBox } from "./VideoBox";
@@ -109,10 +109,10 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 <ObserverJsxParser
                     blacklistedAttrs={[]}
                     components={{
-                        FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView,
+                        FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
                         CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                        PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
-                        ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox,
+                        PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
+                        ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
                         RecommendationsBox, ScreenshotBox
                     }}
                     bindings={this.CreateBindings()}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index ce7bcd206..fc9ee1201 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -34,11 +34,11 @@
         overflow-y: scroll;
         height: calc(100% - 20px);
     }
-    .documentView-docuLinkAnchor {
+    .documentView-linkAnchorBoxAnchor {
         display:flex;
         overflow: hidden;
     }
-    .documentView-docuLinkWrapper {
+    .documentView-linkAnchorBoxWrapper {
         pointer-events: none;
         position: absolute;
         transform-origin: top left;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2df5c9bbd..8ba61fb7e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -296,13 +296,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this: this.props.Document,
                     self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document,
                     thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
-                }, console.log);// && !this.props.Document.isButton && this.select(false);
+                }, console.log);
                 if (this.props.Document !== Doc.UserDoc().undoBtn && this.props.Document !== Doc.UserDoc().redoBtn) {
                     UndoManager.RunInBatch(func, "on click");
                 } else func();
-            } else if (this.Document.type === DocumentType.BUTTON) {
-                UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
-            } else if (this.Document.isButton) {
+            } else if (this.Document.editScriptOnClick) {
+                UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, StrCast(this.Document.editScriptOnClick), e.clientX, e.clientY), "on button click");
+            } else if (this.Document.isLinkButton) {
                 DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
                 if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) {
@@ -327,7 +327,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const targetFocusAfterDocFocus = () => {
                 const where = StrCast(this.Document.followLinkLocation) || followLoc;
                 const hackToCallFinishAfterFocus = () => {
-                    setTimeout(() => finished?.(), 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.  
+                    finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.  
                     return false; // we must return false here so that the zoom to the document is not reversed.  If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
                 };
                 this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
@@ -577,23 +577,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @undoBatch
-    toggleButtonBehavior = (): void => {
-        if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) {
-            this.Document.isButton = false;
+    toggleLinkButtonBehavior = (): void => {
+        if (this.Document.isLinkButton || this.Document.onClick || this.Document.ignoreClick) {
+            this.Document.isLinkButton = false;
             this.Document.ignoreClick = false;
             this.Document.onClick = undefined;
         } else {
-            this.Document.isButton = true;
+            this.Document.isLinkButton = true;
             this.Document.followLinkLocation = undefined;
         }
     }
 
     @undoBatch
     toggleFollowInPlace = (): void => {
-        if (this.Document.isButton) {
-            this.Document.isButton = false;
+        if (this.Document.isLinkButton) {
+            this.Document.isLinkButton = false;
         } else {
-            this.Document.isButton = true;
+            this.Document.isLinkButton = true;
             this.Document.followLinkLocation = "inPlace";
         }
     }
@@ -642,7 +642,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const portal = Docs.Create.FreeformDocument([], { _width: (this.layoutDoc._width || 0) + 10, _height: this.layoutDoc._height || 0, title: StrCast(this.props.Document.title) + ".portal" });
             DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
         }
-        this.Document.isButton = true;
+        this.Document.isLinkButton = true;
     }
 
     @undoBatch
@@ -727,8 +727,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         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.isButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
-        onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleButtonBehavior, icon: "concierge-bell" });
+        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
+        onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
@@ -861,7 +861,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         await Promise.all(allDocs.map((doc: Doc) => {
             let isMainDoc: boolean = false;
             const dataDoc = Doc.GetProto(doc);
-            if (doc.type === DocumentType.TEXT) {
+            if (doc.type === DocumentType.RTF) {
                 if (dataDoc === Doc.GetProto(this.props.Document)) {
                     isMainDoc = true;
                 }
@@ -964,7 +964,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const fallback = Cast(this.props.Document.layoutKey, "string");
         return typeof fallback === "string" ? fallback : "layout";
     }
-    rootSelected = (outsideReaction: boolean) => {
+    rootSelected = (outsideReaction?: boolean) => {
         return this.isSelected(outsideReaction) || (this.props.Document.forceActive && this.props.rootSelected?.(outsideReaction) ? true : false);
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
@@ -1026,10 +1026,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get anchors() {
         TraceMobx();
         return DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
-            <div className="documentView-docuLinkWrapper" key={d[Id]}>
+            <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}>
                 <DocumentView {...this.props}
                     Document={d}
-                    ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the DocuLinkBox
+                    ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     layoutKey={this.linkEndpoint(d)}
@@ -1041,7 +1041,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get innards() {
         TraceMobx();
         if (!this.props.PanelWidth()) {  // this happens when the document is a tree view label
-            return <div className="documentView-docuLinkAnchor" >
+            return <div className="documentView-linkAnchorBoxAnchor" >
                 {StrCast(this.props.Document.title)}
                 {this.anchors}
             </div>;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 37770a2e1..836d95830 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -262,7 +262,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (de.complete.docDragData) {
             const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
             // replace text contents whend dragging with Alt
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
+            if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
                 if (draggedDoc.data instanceof RichTextField) {
                     Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
                     e.stopPropagation();
@@ -1206,7 +1206,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
                         style={{
                             padding: `${NumCast(this.Document._xMargin, 0)}px  ${NumCast(this.Document._yMargin, 0)}px`,
-                            pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                            pointerEvents: ((this.Document.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
                         }} />
                 </div>
                 {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
new file mode 100644
index 000000000..ab5b2c6b3
--- /dev/null
+++ b/src/client/views/nodes/LabelBox.scss
@@ -0,0 +1,38 @@
+.labelBox-outerDiv {
+    width: 100%;
+    height: 100%;
+    pointer-events: all;
+    border-radius: inherit;
+    display: flex;
+    flex-direction: column;
+}
+
+.labelBox-mainButton {
+    width: 100%;
+    height: 100%;
+    border-radius: inherit;
+    letter-spacing: 2px;
+    text-transform: uppercase;
+    overflow: hidden;
+    display:flex;
+}
+
+.labelBox-mainButtonCenter {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: inline;
+    align-items: center;
+    margin: auto;
+}
+
+.labelBox-params {
+    display: flex;
+    flex-direction: row;
+}
+
+.labelBox-missingParam {
+    width: 100%;
+    background: lightgray;
+    border: dimGray solid 1px;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
new file mode 100644
index 000000000..0ec6af93a
--- /dev/null
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -0,0 +1,97 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit } from '@fortawesome/free-regular-svg-icons';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { List } from '../../../new_fields/List';
+import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
+import { ScriptField } from '../../../new_fields/ScriptField';
+import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
+import { DocComponent } from '../DocComponent';
+import './LabelBox.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ContextMenu } from '../ContextMenu';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+
+
+library.add(faEdit as any);
+
+const LabelSchema = createSchema({
+    onClick: ScriptField,
+    buttonParams: listSpec("string"),
+    text: "string"
+});
+
+type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
+const LabelDocument = makeInterface(LabelSchema, documentSchema);
+
+@observer
+export class LabelBox extends DocComponent<FieldViewProps, LabelDocument>(LabelDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
+    private dropDisposer?: DragManager.DragDropDisposer;
+
+    @computed get dataDoc() {
+        return this.props.DataDoc &&
+            (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
+                this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
+    }
+
+
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this.dropDisposer?.();
+        if (ele) {
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+        }
+    }
+
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({
+            description: "Clear Script Params", event: () => {
+                const params = FieldValue(this.Document.buttonParams);
+                params?.map(p => this.props.Document[p] = undefined);
+            }, icon: "trash"
+        });
+
+        ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+    }
+
+    @undoBatch
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        const docDragData = de.complete.docDragData;
+        const params = this.Document.buttonParams;
+        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
+        if (docDragData && missingParams?.includes((e.target as any).textContent)) {
+            this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
+                d.onDragStart ? docDragData.draggedDocuments[i] : d));
+            e.stopPropagation();
+        }
+    }
+    // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
+    render() {
+        const params = this.Document.buttonParams;
+        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
+        params?.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... 
+        return (
+            <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
+                style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
+                <div className="labelBox-mainButton" style={{
+                    background: this.Document.backgroundColor, color: this.Document.color || "inherit",
+                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: (this.Document.textTransform as any) || ""
+                }} >
+                    <div className="labelBox-mainButtonCenter">
+                        {(this.Document.text || this.Document.title)}
+                    </div>
+                </div>
+                <div className="labelBox-params" >
+                    {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
new file mode 100644
index 000000000..7b6093ebd
--- /dev/null
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -0,0 +1,29 @@
+.linkAnchorBox-cont, .linkAnchorBox-cont-small {
+    cursor: default;
+    position: absolute;
+    width: 15;
+    height: 15;
+    border-radius: 20px;
+    pointer-events: all;
+    user-select: none;
+
+    .linkAnchorBox-linkCloser {
+        position: absolute;
+        width: 18;
+        height: 18;
+        background: rgb(219, 21, 21);
+        top: -1px;
+        left: -1px;
+        border-radius: 5px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        padding-left: 2px;
+        padding-top: 1px;
+    }
+}
+
+.linkAnchorBox-cont-small {
+    width:5px;
+    height:5px;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
new file mode 100644
index 000000000..770a3d0d1
--- /dev/null
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -0,0 +1,146 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils, setupMoveUpEvents } from '../../../Utils';
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager } from "../../util/DragManager";
+import { DocComponent } from "../DocComponent";
+import "./LinkAnchorBox.scss";
+import { FieldView, FieldViewProps } from "./FieldView";
+import React = require("react");
+import { ContextMenuProps } from "../ContextMenuItem";
+import { ContextMenu } from "../ContextMenu";
+import { LinkEditor } from "../linking/LinkEditor";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { SelectionManager } from "../../util/SelectionManager";
+import { TraceMobx } from "../../../new_fields/util";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+type LinkAnchorSchema = makeInterface<[typeof documentSchema]>;
+const LinkAnchorDocument = makeInterface(documentSchema);
+
+@observer
+export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); }
+    _doubleTap = false;
+    _lastTap: number = 0;
+    _ref = React.createRef<HTMLDivElement>();
+    _isOpen = false;
+    _timeout: NodeJS.Timeout | undefined;
+    @observable _x = 0;
+    @observable _y = 0;
+    @observable _selected = false;
+    @observable _editing = false;
+    @observable _forceOpen = false;
+
+    onPointerDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, this.onPointerMove, () => { }, this.onClick);
+    }
+    onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
+        const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
+        if (!this._isOpen && cdiv) {
+            const bounds = cdiv.getBoundingClientRect();
+            const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
+            const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
+            const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
+            if (separation > 100) {
+                const dragData = new DragManager.DocumentDragData([this.props.Document]);
+                dragData.dropAction = "alias";
+                dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isButton"];
+                DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
+                return true;
+            } else if (dragdist > separation) {
+                this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
+                this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
+            }
+        }
+        return false;
+    });
+    @action
+    onClick = (e: PointerEvent) => {
+        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0);
+        this._lastTap = Date.now();
+        if ((e.button === 2 || e.ctrlKey || !this.props.Document.isLinkButton)) {
+            this.props.select(false);
+        }
+        if (!this._doubleTap) {
+            const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack!  need a better prop for passing the anchor's container 
+            this._editing = true;
+            anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
+            if (anchorContainerDoc && !this.props.Document.onClick && !this._isOpen) {
+                this._timeout = setTimeout(action(() => {
+                    DocumentManager.Instance.FollowLink(this.props.Document, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
+                    this._editing = false;
+                }), 300 - (Date.now() - this._lastTap));
+            }
+        } else {
+            this._timeout && clearTimeout(this._timeout);
+            this._timeout = undefined;
+        }
+    }
+
+    openLinkDocOnRight = (e: React.MouseEvent) => {
+        this.props.addDocTab(this.props.Document, "onRight");
+    }
+    openLinkTargetOnRight = (e: React.MouseEvent) => {
+        const alias = Doc.MakeAlias(Cast(this.props.Document[this.props.fieldKey], Doc, null));
+        alias.isLinkButton = undefined;
+        alias.isBackground = undefined;
+        alias.layoutKey = "layout";
+        this.props.addDocTab(alias, "onRight");
+    }
+    @action
+    openLinkEditor = action((e: React.MouseEvent) => {
+        SelectionManager.DeselectAll();
+        this._editing = this._forceOpen = true;
+    });
+
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({ description: "Open Link Target on Right", event: () => this.openLinkTargetOnRight(e), icon: "eye" });
+        funcs.push({ description: "Open Link on Right", event: () => this.openLinkDocOnRight(e), icon: "eye" });
+        funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
+
+        ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
+    }
+
+    render() {
+        TraceMobx();
+        const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0;
+        const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0;
+        const c = StrCast(this.props.Document.backgroundColor, "lightblue");
+        const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+        const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
+
+        const timecode = this.props.Document[anchor + "Timecode"];
+        const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
+        const flyout = (
+            <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
+                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
+                {!this._forceOpen ? (null) : <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
+                    <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
+                </div>}
+            </div>
+        );
+        const small = this.props.PanelWidth() <= 1;
+        return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} title={targetTitle} onContextMenu={this.specificContextMenu}
+            ref={this._ref} style={{
+                background: c,
+                left: !small ? `calc(${x}% - 7.5px)` : undefined,
+                top: !small ? `calc(${y}% - 7.5px)` : undefined,
+                transform: `scale(${anchorScale / this.props.ContentScaling()})`
+            }} >
+            {!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" >
+                        <FontAwesomeIcon icon={"eye"} size={"lg"} />
+                    </span>
+                </Flyout>}
+        </div>;
+    }
+}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f8c008a2d..53ad547b6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -9,7 +9,6 @@ import { ScriptField } from '../../../new_fields/ScriptField';
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { PdfField, URLField } from "../../../new_fields/URLField";
 import { Utils } from '../../../Utils';
-import { KeyCodes } from '../../northstar/utils/KeyCodes';
 import { undoBatch } from '../../util/UndoManager';
 import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
@@ -18,10 +17,10 @@ import { DocAnnotatableComponent } from "../DocComponent";
 import { PDFViewer } from "../pdf/PDFViewer";
 import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
+import { KeyCodes } from '../../util/KeyCodes';
 import "./PDFBox.scss";
 import React = require("react");
 import { documentSchema } from '../../../new_fields/documentSchemas';
-import { url } from 'inspector';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 73d09b4e1..e7434feaa 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -6,7 +6,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { InkTool } from "../../../new_fields/InkField";
-import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { returnFalse } from "../../../Utils";
 import { DocumentManager } from "../../util/DocumentManager";
 import { undoBatch } from "../../util/UndoManager";
@@ -246,7 +246,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         });
     }
 
-    updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: number) => {
+    updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: CollectionViewType) => {
         if (BoolCast(this.props.Document.inOverlay) !== (mode === CollectionViewType.Invalid)) {
             if (this.props.Document.inOverlay) {
                 Doc.RemoveDocFromList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document);
@@ -261,7 +261,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         }
     }));
 
-    initializeViewAliases = (docList: Doc[], viewtype: number) => {
+    initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => {
         const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
         docList.forEach(doc => {
             doc.presBox = this.props.Document; // give contained documents a reference to the presentation
@@ -283,14 +283,14 @@ export class PresBox extends React.Component<FieldViewProps> {
     @undoBatch
     viewChanged = action((e: React.ChangeEvent) => {
         //@ts-ignore
-        this.props.Document._viewType = Number(e.target.selectedOptions[0].value);
+        this.props.Document._viewType = e.target.selectedOptions[0].value;
         this.props.Document._viewType === CollectionViewType.Stacking && (this.props.Document._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
         this.updateMinimize(e, Number(this.props.Document._viewType));
     });
 
     childLayoutTemplate = () => this.props.Document._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined;
     render() {
-        const mode = NumCast(this.props.Document._viewType, CollectionViewType.Invalid);
+        const mode = StrCast(this.props.Document._viewType) as CollectionViewType;
         this.initializeViewAliases(this.childDocs, mode);
         return <div className="presBox-cont" style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} >
             <div className="presBox-buttons" style={{ display: this.props.Document._chromeStatus === "disabled" ? "none" : undefined }}>
diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
new file mode 100644
index 000000000..a2dd134ed
--- /dev/null
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -0,0 +1,71 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit } from '@fortawesome/free-regular-svg-icons';
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../../new_fields/Doc';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { CompileScript } from "../../util/Scripting";
+import { ScriptBox } from '../ScriptBox';
+import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
+import { ScriptField } from '../../../new_fields/ScriptField';
+import { BoolCast, ScriptCast } from '../../../new_fields/Types';
+import { DocComponent } from '../DocComponent';
+import { FieldView, FieldViewProps } from './FieldView';
+import './ScriptingBox.scss';
+import { DocumentIconContainer } from './DocumentIcon';
+
+
+library.add(faEdit as any);
+
+const ScriptingSchema = createSchema({
+    onClick: ScriptField,
+    buttonParams: listSpec("string"),
+    text: "string"
+});
+
+type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>;
+const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
+
+@observer
+export class ScriptingBox extends DocComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScriptingBox, fieldKey); }
+
+    @computed get dataDoc() {
+        return this.props.DataDoc &&
+            (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
+                this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
+    }
+
+    specificContextMenu = (e: React.MouseEvent): void => { }
+
+    render() {
+        const script = ScriptCast(this.props.Document[this.props.fieldKey]);
+        let originalText: string | undefined = undefined;
+        if (script) {
+            originalText = script.script.originalScript;
+        }
+        return !(this.props.Document instanceof Doc) ? (null) :
+            <ScriptBox initialText={originalText}
+                setParams={() => { }}
+                onCancel={() => { }}
+                onSave={(text, onError) => {
+                    if (!text) {
+                        this.dataDoc[this.props.fieldKey] = undefined;
+                    } else {
+                        const script = CompileScript(text, {
+                            params: { this: Doc.name },
+                            typecheck: false,
+                            editable: true,
+                            transformer: DocumentIconContainer.getTransformer()
+                        });
+                        if (!script.compiled) {
+                            onError(script.errors.map(error => error.messageText).join("\n"));
+                        }
+                        else {
+                            this.dataDoc[this.props.fieldKey] = new ScriptField(script);
+                        }
+                    }
+                }} showDocumentIcons />;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d384ad12f..a4a330fe6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -132,7 +132,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                         x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
                         _width: 150, _height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
                     });
-                    imageSummary.isButton = true;
+                    imageSummary.isLinkButton = true;
                     this.props.addDocument && this.props.addDocument(imageSummary);
                     DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "video snapshot");
                 }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 720f09fe0..90cd64f3c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -282,7 +282,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
             if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
             if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
             annoDoc.group = mainAnnoDoc;
-            annoDoc.isButton = true;
+            annoDoc.isLinkButton = true;
             annoDocs.push(annoDoc);
             anno.remove();
             mainAnnoDoc = annoDoc;
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 1c05ff864..662b37d77 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -33,7 +33,7 @@ export enum Keys {
 export class FilterBox extends React.Component {
 
     static Instance: FilterBox;
-    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
 
     //if true, any keywords can be used. if false, all keywords are required.
     //this also serves as an indicator if the word status filter is applied
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 46c109934..ec942bf7c 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -26,7 +26,7 @@ library.add(faBan);
 
 @observer
 export class IconBar extends React.Component {
-    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
 
     @observable private _icons: string[] = this._allIcons;
 
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 4f94139d9..52641c543 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -86,15 +86,13 @@ export class IconButton extends React.Component<IconButtonProps>{
                 return faMusic;
             case (DocumentType.COL):
                 return faObjectGroup;
-            case (DocumentType.HIST):
-                return faChartBar;
             case (DocumentType.IMG):
                 return faImage;
             case (DocumentType.LINK):
                 return faLink;
             case (DocumentType.PDF):
                 return faFilePdf;
-            case (DocumentType.TEXT):
+            case (DocumentType.RTF):
                 return faStickyNote;
             case (DocumentType.VID):
                 return faVideo;
@@ -158,15 +156,13 @@ export class IconButton extends React.Component<IconButtonProps>{
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />);
             case (DocumentType.COL):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />);
-            case (DocumentType.HIST):
-                return (<FontAwesomeIcon className="fontawesome-icon" icon={faChartBar} />);
             case (DocumentType.IMG):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />);
             case (DocumentType.LINK):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />);
             case (DocumentType.PDF):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />);
-            case (DocumentType.TEXT):
+            case (DocumentType.RTF):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />);
             case (DocumentType.VID):
                 return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />);
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 67af661c9..19a4d558e 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -128,7 +128,7 @@ export class SearchBox extends React.Component<SearchProps> {
         }
     }
 
-    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
     //if true, any keywords can be used. if false, all keywords are required.
     //this also serves as an indicator if the word status filter is applied
     @observable private _filterOpen: boolean = false;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0d77026ad..fe2000700 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -68,7 +68,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
     getOnClick({ col, target }: { col: Doc, target: Doc }) {
         return () => {
             col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-            if (NumCast(col._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+            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;
@@ -178,14 +178,13 @@ export class SearchItem extends React.Component<SearchItemProps> {
         }
         const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
             layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
-                layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
+                layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote :
                     layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm :
                         layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup :
                             layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic :
                                 layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink :
-                                    layoutresult.indexOf(DocumentType.HIST) !== -1 ? faChartBar :
-                                        layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
-                                            faCaretUp;
+                                    layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
+                                        faCaretUp;
         return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} >
             <FontAwesomeIcon icon={button} size="2x" />
         </div>;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index d381447c5..4fc4dc1cf 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -80,7 +80,7 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
 }
 
 export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
-    return await Cast(field, Doc);
+    return Cast(field, Doc);
 }
 
 export function DocListCast(field: FieldResult): Doc[] {
@@ -713,7 +713,7 @@ export namespace Doc {
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
     export function IsBrushed(doc: Doc) {
         return computedFn(function IsBrushed(doc: Doc) {
-            return brushManager.BrushedDoc.has(doc) ||  brushManager.BrushedDoc.has(Doc.GetProto(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)
@@ -919,7 +919,7 @@ Scripting.addGlobal(function curPresentationItem() {
 Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().SelectedDocs = new List([doc]); });
 Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     const docs = DocListCast(Doc.UserDoc().SelectedDocs).
-        filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCUMENT && d.type !== DocumentType.KVP &&
+        filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCHOLDER && d.type !== DocumentType.KVP &&
             (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
 });
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index ad4a5a252..a5a81f4a4 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,9 +4,6 @@ import { Deserializable } from "../client/util/SerializationHelper";
 import { Copy, ToScriptString, ToPlainText, ToString } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 
-const delimiter = "\n";
-const joiner = "";
-
 @scriptingGlobal
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 4a3119aeb..8d0ddf94c 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -98,12 +98,15 @@ export class ScriptField extends ObjectField {
     [Copy](): ObjectField {
         return new ScriptField(this.script);
     }
+    toString() {
+        return `${this.script.originalScript}`;
+    }
 
     [ToScriptString]() {
         return "script field";
     }
     [ToString]() {
-        return "script field";
+        return this.script.originalScript;
     }
     public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) {
         const compiled = CompileScript(script, {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 03519cb94..216b63352 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -61,7 +61,7 @@ export const documentSchema = createSchema({
     fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
     letterSpacing: "string",
     textTransform: "string",
-    childTemplateName: "string" // the name of a template to use to override the layoutKey when rendering a document in DocumentBox 
+    childTemplateName: "string" // the name of a template to use to override the layoutKey when rendering a document in DocHolderBox 
 });
 
 export const positionSchema = createSchema({
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 81f63656b..01aae5de7 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -19,7 +19,7 @@ export class Message<T> {
 
 export enum Types {
     Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
-    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
+    Html, Video, Audio, Ink, PDF, Tuple, Boolean, Script, Templates
 }
 
 export interface Transferable {
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index a8680c0c9..80e4a6741 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -2,7 +2,6 @@ import RouteSubscriber from "./RouteSubscriber";
 import { DashUserModel } from "./authentication/models/user_model";
 import { Request, Response, Express } from 'express';
 import { cyan, red, green } from 'colors';
-import { Utils } from "../client/northstar/utils/Utils";
 
 export enum Method {
     GET,
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 9f9fc9619..947aa4289 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -199,7 +199,7 @@ export namespace WebSocket {
     function setField(socket: Socket, newValue: Transferable) {
         Database.Instance.update(newValue.id, newValue, () =>
             socket.broadcast.emit(MessageStore.SetField.Message, newValue));
-        if (newValue.type === Types.Text) {
+        if (newValue.type === Types.Text) {  // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
             Search.updateDocument({ id: newValue.id, data: (newValue as any).data });
         }
     }
@@ -221,6 +221,7 @@ export namespace WebSocket {
         "pdf": ["_t", "url"],
         "audio": ["_t", "url"],
         "web": ["_t", "url"],
+        "script": ["_t", value => value.script.originalScript],
         "RichTextField": ["_t", value => value.Text],
         "date": ["_d", value => new Date(value.date).toISOString()],
         "proxy": ["_i", "fieldId"],
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 529f8d56d..589055fbe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -2,8 +2,6 @@ import { action, computed, observable, reaction } from "mobx";
 import * as rp from 'request-promise';
 import { DocServer } from "../../../client/DocServer";
 import { Docs, DocumentOptions } from "../../../client/documents/Documents";
-import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
-import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
 import { UndoManager } from "../../../client/util/UndoManager";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
@@ -419,77 +417,6 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
         });
-        // try {
-        //     const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
-        //     NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
-        //     await Gateway.Instance.ClearCatalog();
-        //     const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
-        //     let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
-        //     let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
-        //     // if (catprom) await Promise.all(catprom);
-        // } catch (e) {
-
-        // }
-    }
-
-    /* Northstar catalog ... really just for testing so this should eventually go away */
-    // --------------- Northstar hooks ------------- /
-    static _northstarSchemas: Doc[] = [];
-    @observable private static _northstarCatalog?: Catalog;
-    @computed public static get NorthstarDBCatalog() { return this._northstarCatalog; }
-
-    @action static SetNorthstarCatalog(ctlog: Catalog, extras: Catalog[]) {
-        CurrentUserUtils.NorthstarDBCatalog = ctlog;
-        // if (ctlog && ctlog.schemas) {
-        //     extras.map(ex => ctlog.schemas!.push(ex));
-        //     return ctlog.schemas.map(async schema => {
-        //         let schemaDocuments: Doc[] = [];
-        //         let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
-        //         await Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
-        //             promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
-        //                 if (field instanceof Doc) {
-        //                     schemaDocuments.push(field);
-        //                 } else {
-        //                     var atmod = new ColumnAttributeModel(attr);
-        //                     let histoOp = new HistogramOperation(schema.displayName!,
-        //                         new AttributeTransformationModel(atmod, AggregateFunction.None),
-        //                         new AttributeTransformationModel(atmod, AggregateFunction.Count),
-        //                         new AttributeTransformationModel(atmod, AggregateFunction.Count));
-        //                     schemaDocuments.push(Docs.Create.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
-        //                 }
-        //             })));
-        //             return promises;
-        //         }, [] as Promise<void>[]));
-        //         return CurrentUserUtils._northstarSchemas.push(Docs.Create.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }));
-        //     });
-        // }
-    }
-    public static set NorthstarDBCatalog(ctlog: Catalog | undefined) { this._northstarCatalog = ctlog; }
-
-    public static AddNorthstarSchema(schema: Schema, schemaDoc: Doc) {
-        if (this._northstarCatalog && CurrentUserUtils._northstarSchemas) {
-            this._northstarCatalog.schemas!.push(schema);
-            CurrentUserUtils._northstarSchemas.push(schemaDoc);
-            const schemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
-            schemas.push(schema.displayName!);
-            CurrentUserUtils.UserDocument.DBSchemas = new List<string>(schemas);
-        }
-    }
-    public static GetNorthstarSchema(name: string): Schema | undefined {
-        return !this._northstarCatalog || !this._northstarCatalog.schemas ? undefined :
-            ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas, (s: Schema) => s.displayName === name);
-    }
-    public static GetAllNorthstarColumnAttributes(schema: Schema) {
-        const recurs = (attrs: Attribute[], g?: AttributeGroup) => {
-            if (g && g.attributes) {
-                attrs.push.apply(attrs, g.attributes);
-                if (g.attributeGroups) {
-                    g.attributeGroups.forEach(ng => recurs(attrs, ng));
-                }
-            }
-            return attrs;
-        };
-        return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined);
     }
 }
 
diff --git a/src/server/updateProtos.ts b/src/server/updateProtos.ts
index 90490eb45..e9860bd61 100644
--- a/src/server/updateProtos.ts
+++ b/src/server/updateProtos.ts
@@ -1,8 +1,7 @@
 import { Database } from "./database";
 
 const protos =
-    ["text", "histogram", "image", "web", "collection", "kvp",
-        "video", "audio", "pdf", "icon", "import", "linkdoc"];
+    ["text", "image", "web", "collection", "kvp", "video", "audio", "pdf", "icon", "import", "linkdoc"];
 
 (async function () {
     await Promise.all(
-- 
cgit v1.2.3-70-g09d2


From e4b22138e39244728144fbdd4c06e439be1b519a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 10 Apr 2020 13:24:07 -0400
Subject: more cleanup for layoutDoc/dataDoc/rootDoc updates. fix to doc
 filtering checkboxes getting filtered. fix to template captions.  fixes to
 generalize LabelDoc's buttonParms

---
 src/client/documents/Documents.ts                  | 10 ++---
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/DropConverter.ts                   |  2 +-
 src/client/util/RichTextRules.ts                   |  2 +-
 src/client/views/DocComponent.tsx                  | 16 +++----
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/InkingStroke.tsx                  |  4 +-
 src/client/views/TemplateMenu.tsx                  |  6 +--
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../views/collections/CollectionTreeView.tsx       | 16 ++++---
 src/client/views/collections/CollectionView.tsx    | 17 ++++----
 .../views/collections/CollectionViewChromes.tsx    | 23 ++--------
 src/client/views/nodes/AudioBox.tsx                |  8 ++--
 src/client/views/nodes/ColorBox.tsx                |  4 +-
 src/client/views/nodes/DocumentBox.tsx             |  6 +--
 src/client/views/nodes/DocumentView.tsx            |  6 +--
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +-
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 src/client/views/nodes/LabelBox.tsx                | 48 +++++++++-----------
 src/client/views/nodes/LinkAnchorBox.tsx           |  4 +-
 src/client/views/nodes/LinkBox.tsx                 |  4 +-
 src/client/views/nodes/PDFBox.tsx                  |  4 +-
 src/client/views/nodes/PresBox.tsx                 |  4 +-
 src/client/views/nodes/QueryBox.tsx                |  4 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  6 +--
 src/client/views/nodes/ScriptingBox.tsx            |  4 +-
 src/client/views/nodes/SliderBox.tsx               | 51 ++++++++++------------
 src/client/views/nodes/VideoBox.tsx                |  4 +-
 src/client/views/nodes/WebBox.tsx                  |  4 +-
 src/client/views/pdf/PDFViewer.tsx                 |  4 +-
 .../views/presentationview/PresElementBox.tsx      |  4 +-
 src/new_fields/Doc.ts                              |  9 ++--
 src/new_fields/RichTextField.ts                    |  4 ++
 34 files changed, 137 insertions(+), 159 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c823c3233..22f22cbfd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -566,8 +566,8 @@ export namespace Docs {
 
             LinkManager.Instance.addLink(doc);
 
-            Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
-            Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
+            Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
+            Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
             return doc;
         }
 
@@ -908,10 +908,10 @@ export namespace DocUtils {
         if (target.doc === CurrentUserUtils.UserDocument) return undefined;
 
         const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship }, id);
-        Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" (" + (this.linkRelationship||"to") +") "  + this.anchor2.title');
+        Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1.title +" (" + (self.linkRelationship||"to") +") "  + self.anchor2.title');
 
-        Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
-        Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
+        Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
+        Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
         return linkDoc;
     }
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c856bbb1e..346eb2fb4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -222,7 +222,7 @@ export namespace DragManager {
             const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
             params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
             initialize?.(bd);
-            bd.buttonParams = new List<string>(params);
+            bd["onclick-params"] = new List<string>(params);
             e.docDragData && (e.docDragData.droppedDocuments = [bd]);
         };
         StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index eb4d3a9b0..8cea120cd 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -35,7 +35,7 @@ export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string
             any = makeTemplate(d, false) || any;
         }
     });
-    if (!docs.length && first) {
+    if (first) {
         any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any;
     }
     if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 8f2df054b..4ccd461be 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -147,7 +147,7 @@ export class RichTextRules {
                     textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                     textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
                     textDocInline.proto = textDoc;  // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
-                    textDocInline._textContext = ComputedField.MakeFunction(`copyField(this.${inlineFieldKey})`, { this: Doc.name });
+                    textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
                     textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
                     textDoc[inlineFieldKey] = ""; // set a default value for the annotation
                     const node = (state.doc.resolve(start) as any).nodeAfter;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d08065820..c97e46f91 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -8,7 +8,7 @@ import { InkTool } from '../../new_fields/InkField';
 import { InteractionUtils } from '../util/InteractionUtils';
 
 
-///  DocComponent returns a generic React base class used by views that don't have any data extensions (e.g.,CollectionFreeFormDocumentView, DocumentView, LabelBox)
+///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 interface DocComponentProps {
     Document: Doc;
     LayoutDoc?: () => Opt<Doc>;
@@ -22,15 +22,15 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
         @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
-        @computed get dataDoc() { return this.props.Document[DataSym]; }
+        @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
 
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
 
-///  DocStaticProps return a base class for React document views that have data extensions but aren't annotatable (e.g. AudioBox, FormattedTextBox)
-interface DocExtendableProps {
+/// FieldViewBoxProps  -  a generic base class for field views that are not annotatable (e.g. AudioBox, FormattedTextBox)
+interface ViewBoxBaseProps {
     Document: Doc;
     DataDoc?: Doc;
     fieldKey: string;
@@ -38,7 +38,7 @@ interface DocExtendableProps {
     renderDepth: number;
     rootSelected: (outsideReaction?: boolean) => boolean;
 }
-export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
+export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         //@computed get Document(): T { return schemaCtor(this.props.Document); }
@@ -60,8 +60,8 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
 }
 
 
-///  DocAnnotatbleComponent return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
-export interface DocAnnotatableProps {
+///  DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
+export interface ViewBoxAnnotatableProps {
     Document: Doc;
     DataDoc?: Doc;
     fieldKey: string;
@@ -71,7 +71,7 @@ export interface DocAnnotatableProps {
     rootSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
 }
-export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
+export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
         @observable _isChildActive = false;
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bd72385ef..c49fe157c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -393,7 +393,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (SelectionManager.SelectedDocuments().length === 1) {
             const selected = SelectionManager.SelectedDocuments()[0];
             if (this._titleControlString.startsWith("=")) {
-                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ this: selected.props.Document }, console.log).result?.toString() || "";
+                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
             }
             if (this._titleControlString.startsWith("#")) {
                 return selected.props.Document[this._titleControlString.substring(1)]?.toString() || "-unset-";
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index ff63a7c33..f66c04e1f 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -4,7 +4,7 @@ import { documentSchema } from "../../new_fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../new_fields/InkField";
 import { makeInterface } from "../../new_fields/Schema";
 import { Cast, StrCast, NumCast } from "../../new_fields/Types";
-import { DocExtendableComponent } from "./DocComponent";
+import { ViewBoxBaseComponent } from "./DocComponent";
 import { InkingControl } from "./InkingControl";
 import "./InkingStroke.scss";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
@@ -22,7 +22,7 @@ type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
 
 @observer
-export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
+export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
     private analyzeStrokes = () => {
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 8fb8e7516..4d7f1e443 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -76,7 +76,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
-        this.props.docViews.forEach(d => Doc.Layout(d.Document)["_show" + template.Name] = event.target.checked ? template.Name.toLowerCase() : "");
+        this.props.docViews.forEach(d => Doc.Layout(d.layoutDoc)["_show" + template.Name] = event.target.checked ? template.Name.toLowerCase() : "");
     }
 
     @action
@@ -87,7 +87,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @undoBatch
     @action
     toggleChrome = (): void => {
-        this.props.docViews.map(dv => Doc.Layout(dv.Document)).forEach(layout =>
+        this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout =>
             layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : StrCast(layout._replacedChrome, "enabled")));
     }
 
@@ -124,7 +124,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
         if (noteTypesDoc) {
-            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(this)`));
+            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self)`));
             this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
                 <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 41f4fb3f0..0c00a1f22 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -120,8 +120,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             return Cast(this.dataField, listSpec(Doc));
         }
         @computed get childDocs() {
-            const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
-            const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+            const docFilters = this.props.ignoreFields?.includes("_docFilters") ? [] : Cast(this.props.Document._docFilters, listSpec("string"), []);
+            const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
             const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
             for (let i = 0; i < docFilters.length; i += 3) {
                 const [key, value, modifiers] = docFilters.slice(i, i + 3);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7edda5a4f..fd84c6498 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -735,16 +735,18 @@ export class CollectionTreeView extends CollectionSubView(Document, undefined as
                 heroView._showTitle = "title";
                 heroView._showTitleHover = "titlehover";
 
-                Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+                Doc.AddDocToList(Doc.UserDoc().expandingButtons as Doc, "data",
                     Docs.Create.FontIconDocument({
-                        _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
-                        dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), title: "hero view", icon: "portrait"
+                        title: "hero view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
+                        dragFactory: heroView, removeDropProperties: new List<string>(["dropAction"]), icon: "portrait",
+                        onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
                     }));
 
-                Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data",
+                Doc.AddDocToList(Doc.UserDoc().expandingButtons as Doc, "data",
                     Docs.Create.FontIconDocument({
-                        _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
-                        dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "file-alt"
+                        title: "detail view", _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias",
+                        dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), icon: "file-alt",
+                        onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
                     }));
 
                 Document.childLayout = heroView;
@@ -837,7 +839,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
         Docs.Create.TextDocument("", {
             title: facetValue.toString(),
             treeViewChecked: ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)",
-                { layoutDoc: Doc.name, facetHeader: "string", facetValue: "string" },
+                {},
                 { layoutDoc, facetHeader, facetValue })
         }));
     return new List<Doc>(facetValueDocSet);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8192e6751..821840e0b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -354,14 +354,15 @@ export class CollectionView extends Touchable<FieldViewProps> {
             let newFacet: Opt<Doc>;
             if (nonNumbers / allCollectionDocs.length < .1) {
                 newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+                const newFacetField = Doc.LayoutFieldKey(newFacet);
                 const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
                 Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
                 newFacet.treeViewExpandedView = "layout";
                 newFacet.treeViewOpen = true;
-                newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
-                newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
-                newFacet._sliderMinThumb = minVal;
-                newFacet._sliderMaxThumb = maxVal;
+                newFacet[newFacetField + "-min"] = ranged === undefined ? minVal : ranged[0];
+                newFacet[newFacetField + "-max"] = ranged === undefined ? maxVal : ranged[1];
+                Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = minVal;
+                Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = maxVal;
                 newFacet.target = this.props.Document;
                 const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
                 newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
@@ -370,14 +371,12 @@ export class CollectionView extends Touchable<FieldViewProps> {
             } else {
                 newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
                 const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
-                const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
-                newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+                newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables);
             }
             newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
         }
     }
 
-
     onPointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
             this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
@@ -385,11 +384,11 @@ export class CollectionView extends Touchable<FieldViewProps> {
         }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
     }
     filterBackground = () => "dimGray";
+    get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
     @computed get scriptField() {
         const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
         return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
     }
-    @computed get treeIgnoreFields() { return ["_facetCollection", "_docFilters"]; }
     @computed get filterView() {
         const facetCollection = this.props.Document;
         const flyout = (
@@ -419,7 +418,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                         NativeWidth={returnZero}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
-                        ignoreFields={this.treeIgnoreFields}
+                        ignoreFields={this.ignoreFields}
                         annotationsKey={""}
                         dontRegisterView={true}
                         PanelWidth={this.facetWidth}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index b2ca3c19f..ba95dce00 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -361,28 +361,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         }, emptyFunction, emptyFunction);
     }
     dragCommandDown = (e: React.PointerEvent) => {
-        this._startDragPosition = { x: e.clientX, y: e.clientY };
-        document.addEventListener("pointermove", this.dragPointerMove);
-        document.addEventListener("pointerup", this.dragPointerUp);
-        e.stopPropagation();
-        e.preventDefault();
-    }
-
-    dragPointerMove = (e: PointerEvent) => {
-        e.stopPropagation();
-        e.preventDefault();
-        const [dx, dy] = [e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y];
-        if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+        setupMoveUpEvents(this, e, (e, down, delta) => {
             this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
                 DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
                     { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
-            document.removeEventListener("pointermove", this.dragPointerMove);
-            document.removeEventListener("pointerup", this.dragPointerUp);
-        }
-    }
-    dragPointerUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.dragPointerMove);
-        document.removeEventListener("pointerup", this.dragPointerUp);
+            return true;
+        }, emptyFunction, emptyFunction);
+        this._startDragPosition = { x: e.clientX, y: e.clientY };
     }
 
     render() {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c08a2b808..7078cc01c 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
 import "./AudioBox.scss";
 import { Cast, DateCast, NumCast } from "../../../new_fields/Types";
 import { AudioField, nullAudio } from "../../../new_fields/URLField";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { documentSchema } from "../../../new_fields/documentSchemas";
 import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils";
@@ -39,7 +39,7 @@ type AudioDocument = makeInterface<[typeof documentSchema, typeof audioSchema]>;
 const AudioDocument = makeInterface(documentSchema, audioSchema);
 
 @observer
-export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
+export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument>(AudioDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
     public static Enabled = false;
 
@@ -194,8 +194,8 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
             _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height)
         });
         Doc.GetProto(newDoc).recordingSource = this.dataDoc;
-        Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`this.recordingSource["${this.props.fieldKey}-recordingStart"]`);
-        Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("this.recordingSource.audioState");
+        Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
+        Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("self.recordingSource.audioState");
         this.props.addDocument?.(newDoc);
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index b6d687f27..877dfec2d 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -6,7 +6,7 @@ import { makeInterface } from "../../../new_fields/Schema";
 import { StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { SelectionManager } from "../../util/SelectionManager";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
 import "./ColorBox.scss";
 import { FieldView, FieldViewProps } from './FieldView';
@@ -15,7 +15,7 @@ type ColorDocument = makeInterface<[typeof documentSchema]>;
 const ColorDocument = makeInterface(documentSchema);
 
 @observer
-export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocument>(ColorDocument) {
+export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument>(ColorDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
 
     render() {
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 4f2b3b656..ac562f19a 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -9,7 +9,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyPath } from "../../../Utils";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 import "./DocumentBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
@@ -22,7 +22,7 @@ type DocHolderBoxSchema = makeInterface<[typeof documentSchema]>;
 const DocHolderBoxDocument = makeInterface(documentSchema);
 
 @observer
-export class DocHolderBox extends DocAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
+export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); }
     _prevSelectionDisposer: IReactionDisposer | undefined;
     _selections: Doc[] = [];
@@ -54,7 +54,7 @@ export class DocHolderBox extends DocAnnotatableComponent<FieldViewProps, DocHol
         this.contentDoc[this.props.fieldKey] = this.props.Document[this.props.fieldKey];
     }
     showSelection = () => {
-        this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(this,this.excludeCollections,[_last_])?.[0]`);
+        this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`);
     }
     isSelectionLocked = () => {
         const kvpstring = Field.toKeyValueString(this.contentDoc, this.props.fieldKey);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1bccce054..1e22dbc0b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -298,7 +298,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             } else if (this.onClickHandler?.script) {
                 SelectionManager.DeselectAll();
                 const func = () => this.onClickHandler.script.run({
-                    this: this.props.Document,
+                    this: this.layoutDoc,
                     self: this.rootDoc,
                     thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
                 }, console.log);
@@ -512,8 +512,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onPointerUp = (e: PointerEvent): void => {
         this.cleanUpInteractions();
 
-        if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-            this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+        if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+            this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
             document.removeEventListener("pointerup", this.onPointerUp);
             return;
         }
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index bd7ea4c92..9329cf210 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -56,7 +56,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
                 background: StrCast(referenceLayout.backgroundColor),
                 boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
             }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon} color={this._foregroundColor} size="sm" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={this._foregroundColor} size="sm" />
         </button>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index fb919a716..d641dc791 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -38,7 +38,7 @@ import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentButtonBar } from '../DocumentButtonBar';
 import { InkingControl } from "../InkingControl";
 import { AudioBox } from './AudioBox';
@@ -69,7 +69,7 @@ const RichTextDocument = makeInterface(richTextSchema, documentSchema);
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 @observer
-export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bb7d78ace..5b0edf34f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -24,7 +24,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../../views/ContextMenu";
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
@@ -65,7 +65,7 @@ const uploadIcons = {
 };
 
 @observer
-export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
+export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
     protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 0ec6af93a..b9701f923 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -7,10 +7,10 @@ import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { List } from '../../../new_fields/List';
 import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types';
+import { BoolCast, StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
-import { DocComponent } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import './LabelBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -20,27 +20,16 @@ import { documentSchema } from '../../../new_fields/documentSchemas';
 
 library.add(faEdit as any);
 
-const LabelSchema = createSchema({
-    onClick: ScriptField,
-    buttonParams: listSpec("string"),
-    text: "string"
-});
+const LabelSchema = createSchema({});
 
 type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
 const LabelDocument = makeInterface(LabelSchema, documentSchema);
 
 @observer
-export class LabelBox extends DocComponent<FieldViewProps, LabelDocument>(LabelDocument) {
+export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument>(LabelDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
     private dropDisposer?: DragManager.DragDropDisposer;
 
-    @computed get dataDoc() {
-        return this.props.DataDoc &&
-            (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
-                this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
-    }
-
-
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer?.();
         if (ele) {
@@ -52,8 +41,8 @@ export class LabelBox extends DocComponent<FieldViewProps, LabelDocument>(LabelD
         const funcs: ContextMenuProps[] = [];
         funcs.push({
             description: "Clear Script Params", event: () => {
-                const params = FieldValue(this.Document.buttonParams);
-                params?.map(p => this.props.Document[p] = undefined);
+                const params = Cast(this.dataDoc[this.fieldKey + "-params"], listSpec("string"), []);
+                params?.map(p => this.dataDoc[p] = undefined);
             }, icon: "trash"
         });
 
@@ -64,32 +53,35 @@ export class LabelBox extends DocComponent<FieldViewProps, LabelDocument>(LabelD
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         const docDragData = de.complete.docDragData;
-        const params = this.Document.buttonParams;
-        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
+        const params = Cast(this.dataDoc[this.fieldKey + "-params"], listSpec("string"), []);
+        const missingParams = params?.filter(p => this.dataDoc[p] === undefined);
         if (docDragData && missingParams?.includes((e.target as any).textContent)) {
-            this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
+            this.dataDoc[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
                 d.onDragStart ? docDragData.draggedDocuments[i] : d));
             e.stopPropagation();
         }
     }
     // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
     render() {
-        const params = this.Document.buttonParams;
-        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
-        params?.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... 
+        const params = Cast(this.dataDoc[this.fieldKey + "-params"], listSpec("string"), []);
+        const missingParams = params?.filter(p => this.dataDoc[p] === undefined);
+        params?.map(p => DocListCast(this.dataDoc[p])); // bcz: really hacky form of prefetching ... 
         return (
             <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
-                style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
+                style={{ boxShadow: this.layoutDoc.opacity ? StrCast(this.layoutDoc.boxShadow) : "" }}>
                 <div className="labelBox-mainButton" style={{
-                    background: this.Document.backgroundColor, color: this.Document.color || "inherit",
-                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: (this.Document.textTransform as any) || ""
+                    background: StrCast(this.layoutDoc.backgroundColor),
+                    color: StrCast(this.layoutDoc.color, "inherit"),
+                    fontSize: NumCast(this.layoutDoc.fontSize) || "inherit",
+                    letterSpacing: StrCast(this.layoutDoc.letterSpacing),
+                    textTransform: StrCast(this.layoutDoc.textTransform) as any
                 }} >
                     <div className="labelBox-mainButtonCenter">
-                        {(this.Document.text || this.Document.title)}
+                        {StrCast(this.layoutDoc.text, StrCast(this.layoutDoc.title))}
                     </div>
                 </div>
                 <div className="labelBox-params" >
-                    {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
+                    {!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index f3e099dd6..13ffc6956 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -7,7 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { Utils, setupMoveUpEvents } from '../../../Utils';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import "./LinkAnchorBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
@@ -25,7 +25,7 @@ type LinkAnchorSchema = makeInterface<[typeof documentSchema]>;
 const LinkAnchorDocument = makeInterface(documentSchema);
 
 @observer
-export class LinkAnchorBox extends DocExtendableComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) {
+export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); }
     _doubleTap = false;
     _lastTap: number = 0;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 542c86049..af4bf420f 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -4,7 +4,7 @@ import { documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface, listSpec } from "../../../new_fields/Schema";
 import { returnFalse, returnZero } from "../../../Utils";
 import { CollectionTreeView } from "../collections/CollectionTreeView";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./LinkBox.scss";
 import { Cast } from "../../../new_fields/Types";
@@ -13,7 +13,7 @@ type LinkDocument = makeInterface<[typeof documentSchema]>;
 const LinkDocument = makeInterface(documentSchema);
 
 @observer
-export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument>(LinkDocument) {
+export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(LinkDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
     render() {
         return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f2a3e1484..6db36e43c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -13,7 +13,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { PDFViewer } from "../pdf/PDFViewer";
 import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
@@ -26,7 +26,7 @@ type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, t
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 
 @observer
-export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
     private _keyValue: string = "";
     private _valueValue: string = "";
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index bea3170ac..e428e16da 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -16,7 +16,7 @@ import { CollectionView, CollectionViewType } from "../collections/CollectionVie
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./PresBox.scss";
-import { DocExtendableComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { makeInterface } from "../../../new_fields/Schema";
 
 library.add(faArrowLeft);
@@ -33,7 +33,7 @@ type PresBoxSchema = makeInterface<[typeof documentSchema]>;
 const PresBoxDocument = makeInterface(documentSchema);
 
 @observer
-export class PresBox extends DocExtendableComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
+export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
     _childReaction: IReactionDisposer | undefined;
     @observable _isChildActive = false;
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 7016b4f04..947167200 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -6,7 +6,7 @@ import { Id } from '../../../new_fields/FieldSymbols';
 import { makeInterface } from "../../../new_fields/Schema";
 import { StrCast } from "../../../new_fields/Types";
 import { SelectionManager } from "../../util/SelectionManager";
-import { DocAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { SearchBox } from "../search/SearchBox";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./QueryBox.scss";
@@ -15,7 +15,7 @@ type QueryDocument = makeInterface<[typeof documentSchema]>;
 const QueryDocument = makeInterface(documentSchema);
 
 @observer
-export class QueryBox extends DocAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
+export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); }
     _docListChangedReaction: IReactionDisposer | undefined;
     componentDidMount() {
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 1a991f1eb..11b24b059 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -14,7 +14,7 @@ import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocComponent } from "../DocComponent";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
@@ -26,7 +26,7 @@ const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
 library.add(faVideo);
 
 @observer
-export class ScreenshotBox extends DocComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
+export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
     private _reactionDisposer?: IReactionDisposer;
     private _videoRef: HTMLVideoElement | null = null;
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
@@ -109,7 +109,7 @@ export class ScreenshotBox extends DocComponent<FieldViewProps, ScreenshotDocume
     }
     @observable _screenCapture = false;
     specificContextMenu = (e: React.MouseEvent): void => {
-        const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+        const field = Cast(this.dataDoc[this.fieldKey], VideoField);
         if (field) {
             const url = field.url.href;
             const subitems: ContextMenuProps[] = [];
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 309ee3620..70f29e1dc 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -7,7 +7,7 @@ import { ScriptField } from "../../../new_fields/ScriptField";
 import { StrCast, ScriptCast, Cast } from "../../../new_fields/Types";
 import { InteractionUtils } from "../../util/InteractionUtils";
 import { CompileScript, isCompileError, ScriptParam } from "../../util/Scripting";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { EditableView } from "../EditableView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./ScriptingBox.scss";
@@ -20,7 +20,7 @@ type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentS
 const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
 
 @observer
-export class ScriptingBox extends DocAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
+export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
 
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 844d95d11..746ea0b64 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -1,22 +1,20 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit } from '@fortawesome/free-regular-svg-icons';
-import { computed, runInAction } from 'mobx';
+import { runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Handles, Rail, Slider, Tracks, Ticks } from 'react-compound-slider';
-import { Doc } from '../../../new_fields/Doc';
+import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
 import { documentSchema } from '../../../new_fields/documentSchemas';
-import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, FieldValue, StrCast, NumCast, Cast } from '../../../new_fields/Types';
-import { DragManager } from '../../util/DragManager';
+import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
-import './SliderBox.scss';
-import { Handle, TooltipRail, Track, Tick } from './SliderBox-components';
-import { FieldView, FieldViewProps } from './FieldView';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { ScriptBox } from '../ScriptBox';
+import { FieldView, FieldViewProps } from './FieldView';
+import { Handle, Tick, TooltipRail, Track } from './SliderBox-components';
+import './SliderBox.scss';
 
 
 library.add(faEdit as any);
@@ -32,36 +30,33 @@ type SliderDocument = makeInterface<[typeof SliderSchema, typeof documentSchema]
 const SliderDocument = makeInterface(SliderSchema, documentSchema);
 
 @observer
-export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(SliderDocument) {
+export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocument>(SliderDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); }
-    private dropDisposer?: DragManager.DragDropDisposer;
-
-    @computed get dataDoc() {
-        return this.props.DataDoc &&
-            (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
-                this.props.DataDoc.layout === this.Document) ? this.props.DataDoc : Doc.GetProto(this.Document);
-    }
 
+    get minThumbKey() { return this.fieldKey + "-minThumb"; }
+    get maxThumbKey() { return this.fieldKey + "-maxThumb"; }
+    get minKey() { return this.fieldKey + "-min"; }
+    get maxKey() { return this.fieldKey + "-max"; }
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) });
         ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" });
     }
     onChange = (values: readonly number[]) => runInAction(() => {
-        this.Document._sliderMinThumb = values[0];
-        this.Document._sliderMaxThumb = values[1];
-        Cast(this.Document.onThumbChanged, ScriptField, null)?.script.run({ range: values, this: this.props.Document });
+        this.dataDoc[this.minThumbKey] = values[0];
+        this.dataDoc[this.maxThumbKey] = values[1];
+        Cast(this.layoutDoc.onThumbChanged, ScriptField, null)?.script.run({ self: this.rootDoc, range: values, this: this.layoutDoc });
     })
 
     render() {
-        const domain = [NumCast(this.props.Document._sliderMin), NumCast(this.props.Document._sliderMax)];
-        const defaultValues = [NumCast(this.props.Document._sliderMinThumb), NumCast(this.props.Document._sliderMaxThumb)];
-        return (
+        const domain = [NumCast(this.layoutDoc[this.minKey]), NumCast(this.layoutDoc[this.maxKey])];
+        const defaultValues = [NumCast(this.dataDoc[this.minThumbKey]), NumCast(this.dataDoc[this.maxThumbKey])];
+        return domain[1] <= domain[0] ? (null) : (
             <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()}
-                style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
+                style={{ boxShadow: this.layoutDoc.opacity === 0 ? undefined : StrCast(this.layoutDoc.boxShadow, "") }}>
                 <div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
-                    background: this.Document.backgroundColor, color: this.Document.color || "black",
-                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || ""
+                    background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
+                    fontSize: NumCast(this.layoutDoc.fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
                 }} >
                     <Slider
                         mode={2}
@@ -77,7 +72,7 @@ export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(Slid
                             {({ handles, activeHandleID, getHandleProps }) => (
                                 <div className="slider-handles">
                                     {handles.map((handle, i) => {
-                                        const value = i === 0 ? this.Document._sliderMinThumb : this.Document._sliderMaxThumb;
+                                        const value = i === 0 ? defaultValues[0] : defaultValues[1];
                                         return (
                                             <div title={String(value)}>
                                                 <Handle
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 2c67883e9..588068334 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -16,7 +16,7 @@ import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
@@ -33,7 +33,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
 library.add(faVideo);
 
 @observer
-export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
     static _youtubeIframeCounter: number = 0;
     private _reactionDisposer?: IReactionDisposer;
     private _youtubeReactionDisposer?: IReactionDisposer;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index ea5d601ec..55ad7eb0f 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { SelectionManager } from "../../util/SelectionManager";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
@@ -33,7 +33,7 @@ type WebDocument = makeInterface<[typeof documentSchema]>;
 const WebDocument = makeInterface(documentSchema);
 
 @observer
-export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
+export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
 
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
     @observable private collapsed: boolean = true;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f93c1fa97..c49e6512a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -23,7 +23,7 @@ import Annotation from "./Annotation";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
-import { DocAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { documentSchema } from "../../../new_fields/documentSchemas";
 import { DocumentDecorations } from "../DocumentDecorations";
@@ -79,7 +79,7 @@ interface IViewerProps {
  * Handles rendering and virtualization of the pdf
  */
 @observer
-export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
+export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number, height: number }[] = [];
     @observable private _annotations: Doc[] = [];
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index bed181ab9..dd0cbf929 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -12,7 +12,7 @@ import { Cast, NumCast } from "../../../new_fields/Types";
 import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionView';
-import { DocExtendableComponent } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import "./PresElementBox.scss";
@@ -44,7 +44,7 @@ const PresDocument = makeInterface(presSchema, documentSchema);
  * It involves some functionality for its buttons and options.
  */
 @observer
-export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresDocument>(PresDocument) {
+export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
 
     _heightDisposer: IReactionDisposer | undefined;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a9c97fc19..934ba0f60 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -645,11 +645,12 @@ export namespace Doc {
             Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
             (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue));
         }
-        if (templateCaptionValue instanceof RichTextField && (templateCaptionValue.Text || templateCaptionValue.Data.toString().includes("dashField"))) {
-            templateField["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`, { this: Doc.name });
+        // copy the textTemplates from 'this' (not 'self') because the layout contains the template info, not the original doc
+        if (templateCaptionValue instanceof RichTextField && !templateCaptionValue.Empty()) {
+            templateField["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`);
         }
-        if (templateFieldValue instanceof RichTextField && (templateFieldValue.Text || templateFieldValue.Data.toString().includes("dashField"))) {
-            templateField[metadataFieldKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${metadataFieldKey})`, { this: Doc.name });
+        if (templateFieldValue instanceof RichTextField && !templateFieldValue.Empty()) {
+            templateField[metadataFieldKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${metadataFieldKey})`);
         }
 
         // get the layout string that the template uses to specify its layout
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index a5a81f4a4..5cf0e0cc3 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -19,6 +19,10 @@ export class RichTextField extends ObjectField {
         this.Text = text;
     }
 
+    Empty() {
+        return !(this.Text || this.Data.toString().includes("dashField"));
+    }
+
     [Copy]() {
         return new RichTextField(this.Data, this.Text);
     }
-- 
cgit v1.2.3-70-g09d2


From 22d1499703ddb20d0f937364085e46fefcc663da Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 Apr 2020 19:23:21 -0400
Subject: added nudging of documents

---
 src/client/views/GlobalKeyHandler.ts               | 35 +++++++++++++++++-----
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  5 ++++
 src/client/views/nodes/DocumentView.tsx            |  1 +
 4 files changed, 34 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 52801b570..d01f3f1e5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -8,7 +8,7 @@ import { Doc } from "../../new_fields/Doc";
 import { DictationManager } from "../util/DictationManager";
 import SharingManager from "../util/SharingManager";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-import { Cast, PromiseValue } from "../../new_fields/Types";
+import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
 import { ScriptField } from "../../new_fields/ScriptField";
 import { InkingControl } from "./InkingControl";
 import { InkTool } from "../../new_fields/InkField";
@@ -89,13 +89,20 @@ export default class KeyManager {
                         return { stopPropagation: false, preventDefault: false };
                     }
                 }
-                UndoManager.RunInBatch(() => {
-                    SelectionManager.SelectedDocuments().map(docView => {
-                        const doc = docView.props.Document;
-                        const remove = docView.props.removeDocument;
-                        remove && remove(doc);
-                    });
-                }, "delete");
+                UndoManager.RunInBatch(() =>
+                    SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
+                break;
+            case "arrowleft":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-1, 0)), "nudge left");
+                break;
+            case "arrowright":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(1, 0)), "nudge right");
+                break;
+            case "arrowup":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -1)), "nudge up");
+                break;
+            case "arrowdown":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 1)), "nudge down");
                 break;
         }
 
@@ -114,6 +121,18 @@ export default class KeyManager {
             //     DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
             //     stopPropagation = true;
             //     preventDefault = true;
+            case "arrowleft":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-10, 0)), "nudge left");
+                break;
+            case "arrowright":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(10, 0)), "nudge right");
+                break;
+            case "arrowup":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, -10)), "nudge up");
+                break;
+            case "arrowdown":
+                UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(0, 10)), "nudge down");
+                break;
         }
 
         return {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0c00a1f22..6b7c3de56 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -381,7 +381,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                     alert(`Upload failed: ${result.message}`);
                     return;
                 }
-                const full = { ...options, _width: 300, title: name };
+                const full = { ...options, _width: 400, title: name };
                 const pathname = Utils.prepend(result.accessPaths.agnostic.client);
                 const doc = await Docs.Get.DocumentFromType(type, pathname, full);
                 if (!doc) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f9f5f449c..3503af442 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -59,6 +59,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
         return undefined;
     }
+    nudge = (x: number, y: number) => {
+        this.layoutDoc.x = NumCast(this.layoutDoc.x) + x;
+        this.layoutDoc.y = NumCast(this.layoutDoc.y) + y;
+    }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
     panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.());
@@ -93,6 +97,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
             {!this.props.fitToBox ?
                 <DocumentView {...this.props}
+                    nudge={this.nudge}
                     dragDivName={"collectionFreeFormDocumentView-container"}
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 05ff5a371..3784c5c9c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -71,6 +71,7 @@ export interface DocumentViewProps {
     onPointerUp?: ScriptField;
     dropAction?: dropActionType;
     dragDivName?: string;
+    nudge?: (x: number, y: number) => void;
     addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
     moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
-- 
cgit v1.2.3-70-g09d2


From b235c94614141752c98d24a395682a02297d9e6a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Apr 2020 11:15:59 -0400
Subject: cleaned up rootSelected() to work with templates.  adjusted nudge to
 favor moving documents over panning collection

---
 src/client/views/DocComponent.tsx                       |  4 ++--
 src/client/views/PreviewCursor.tsx                      | 14 +++++---------
 src/client/views/collections/CollectionSubView.tsx      |  2 +-
 src/client/views/collections/CollectionView.tsx         |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx       | 17 +++++++++++------
 .../collections/collectionFreeForm/MarqueeView.tsx      |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx      |  4 ++--
 src/client/views/nodes/DocumentView.tsx                 | 16 +++++++++-------
 8 files changed, 32 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b1bd4191c..f19f9308a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -53,7 +53,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -114,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ecebadd2a..92ba13d2f 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -13,7 +13,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _getTransform: () => Transform;
     static _addLiveTextDoc: (doc: Doc) => void;
     static _addDocument: (doc: Doc) => boolean;
-    static _nudge: (x: number, y: number) => void;
+    static _nudge: (x: number, y: number) => boolean;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
@@ -91,17 +91,13 @@ export class PreviewCursor extends React.Component<{}> {
             }
         } else if (PreviewCursor.Visible) {
             if (e.key === "ArrowRight") {
-                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0);
-                e.stopPropagation();
+                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === "ArrowLeft") {
-                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0);
-                e.stopPropagation();
+                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === "ArrowUp") {
-                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1));
-                e.stopPropagation();
+                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             } else if (e.key === "ArrowDown") {
-                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1));
-                e.stopPropagation();
+                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             }
         }
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6b7c3de56..37cf942c6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -100,7 +100,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         }
 
         rootSelected = (outsideReaction?: boolean) => {
-            return this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected(outsideReaction) : false);
+            return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction));
         }
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 821840e0b..86df9c080 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -102,7 +102,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField as any as CollectionViewType;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.props.Document.forceActive || this._isChildActive || this.props.renderDepth === 0;
 
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7dca19073..a77f09163 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -43,6 +43,7 @@ import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
+import { CollectionViewType } from "../CollectionView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -860,7 +861,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             LibraryPath: this.libraryPath,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
-            rootSelected: this.rootSelected,
+            rootSelected: childData ? this.rootSelected : returnFalse,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
@@ -1105,10 +1106,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     _nudgeTime = 0;
     nudge = action((x: number, y: number) => {
-        this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
-            NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
-        this._nudgeTime = Date.now();
-        setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
+            this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+            this._nudgeTime = Date.now();
+            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+            return true;
+        }
+        return false;
     });
     @computed get marqueeView() {
         return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5a6c8573b..9dd3d640b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,7 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay?: boolean;
-    nudge:(x:number, y:number) => void;
+    nudge: (x: number, y: number) => boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3503af442..e7cb2c015 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -60,8 +60,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
     nudge = (x: number, y: number) => {
-        this.layoutDoc.x = NumCast(this.layoutDoc.x) + x;
-        this.layoutDoc.y = NumCast(this.layoutDoc.y) + y;
+        this.props.Document.x = NumCast(this.props.Document.x) + x;
+        this.props.Document.y = NumCast(this.props.Document.y) + y;
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3784c5c9c..cc3c39154 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -289,13 +289,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let preventDefault = true;
             this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
-                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
-                    fullScreenAlias.layoutKey = "layout_fullScreen";
+                if (!(e.nativeEvent as any).formattedHandled) {
+                    const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+                    if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+                        fullScreenAlias.layoutKey = "layout_fullScreen";
+                    }
+                    UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
+                    SelectionManager.DeselectAll();
+                    Doc.UnBrushDoc(this.props.Document);
                 }
-                UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
-                SelectionManager.DeselectAll();
-                Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler?.script) {
                 SelectionManager.DeselectAll();
                 const func = () => this.onClickHandler.script.run({
@@ -978,7 +980,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return typeof fallback === "string" ? fallback : "layout";
     }
     rootSelected = (outsideReaction?: boolean) => {
-        return this.isSelected(outsideReaction) || (this.props.Document.forceActive && this.props.rootSelected?.(outsideReaction) ? true : false);
+        return this.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected?.(outsideReaction));
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     panelWidth = () => this.props.PanelWidth();
-- 
cgit v1.2.3-70-g09d2


From 30db3b71d3661d7cd41ed32f4004069cb10ec2b4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 22 Apr 2020 12:31:40 -0400
Subject: added raw text field for PDFs.  fixed search scrolling in text boxes.

---
 src/client/views/collections/CollectionSubView.tsx |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        | 13 +++++--------
 src/server/DashUploadUtils.ts                      |  7 ++++---
 src/server/SharedMediaTypes.ts                     |  1 +
 4 files changed, 11 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 37cf942c6..ca38a21b8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -388,6 +388,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                     continue;
                 }
                 const proto = Doc.GetProto(doc);
+                proto.text = result.rawText;
                 proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
                 if (Upload.isImageInformation(result)) {
                     proto["data-nativeWidth"] = (result.nativeWidth > result.nativeHeight) ? 400 * result.nativeWidth / result.nativeHeight : 400;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 62911dc5c..c8eb4c23c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -596,7 +596,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         this.setupEditor(this.config, this.props.fieldKey);
 
-        this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch,
+        this._searchReactionDisposer = reaction(() => this.rootDoc.searchMatch,
             search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
             { fireImmediately: true });
 
@@ -821,13 +821,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             this._editorView = new EditorView(this.ProseRef, {
                 state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
                 handleScrollToSelection: (editorView) => {
-                    const ref = editorView.domAtPos(editorView.state.selection.from);
-                    let refNode = ref.node as any;
-                    while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
-                    const r1 = refNode?.getBoundingClientRect();
-                    const r3 = self._ref.current!.getBoundingClientRect();
-                    if (r1.top < r3.top || r1.top > r3.bottom) {
-                        r1 && (self._scrollRef.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+                    const docPos = editorView.coordsAtPos(editorView.state.selection.from);
+                    const viewRect = self._ref.current!.getBoundingClientRect();
+                    if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
+                        docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
                     }
                     return true;
                 },
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 9b518749c..3f903a861 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -100,7 +100,7 @@ export namespace DashUploadUtils {
             const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
             writeStream.write(result.text, error => error ? reject(error) : resolve());
         });
-        return MoveParsedFile(file, Directory.pdfs);
+        return MoveParsedFile(file, Directory.pdfs, undefined, result.text);
     }
 
     const manualSuffixes = [".webm"];
@@ -237,7 +237,7 @@ export namespace DashUploadUtils {
      * @param suffix If the file doesn't have a suffix and you want to provide it one
      * to appear in the new location
      */
-    export async function MoveParsedFile(file: File, destination: Directory, suffix: string | undefined = undefined): Promise<Upload.FileResponse> {
+    export async function MoveParsedFile(file: File, destination: Directory, suffix: string | undefined = undefined, text?: string): Promise<Upload.FileResponse> {
         const { path: sourcePath } = file;
         let name = path.basename(sourcePath);
         suffix && (name += suffix);
@@ -249,7 +249,8 @@ export namespace DashUploadUtils {
                     result: error ? error : {
                         accessPaths: {
                             agnostic: getAccessPaths(destination, name)
-                        }
+                        },
+                        rawText: text
                     }
                 });
             });
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 2495123b7..0f788f6c5 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -21,6 +21,7 @@ export namespace Upload {
 
     export interface FileInformation {
         accessPaths: AccessPathInfo;
+        rawText?: string;
     }
 
     export type FileResponse<T extends FileInformation = FileInformation> = { source: File, result: T | Error };
-- 
cgit v1.2.3-70-g09d2


From a51439a6569079ca2652ce28745317fecce3a353 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 10:53:51 -0400
Subject: changing how multiple images are imported -- now a pile.  imagebox
 chooses render quality based on image width.

---
 src/client/views/collections/CollectionSubView.tsx | 11 +++++++++--
 .../collections/collectionFreeForm/MarqueeView.tsx | 22 ++--------------------
 src/client/views/nodes/ImageBox.tsx                |  4 ++--
 src/new_fields/Doc.ts                              | 21 ++++++++++++++++++++-
 4 files changed, 33 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ca38a21b8..78ba3d6e6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -398,8 +398,15 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 generatedDocuments.push(doc);
             }
             if (generatedDocuments.length) {
-                generatedDocuments.forEach(addDocument);
-                completed && completed();
+                const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
+                if (set) {
+                    const pile = Docs.Create.FreeformDocument(generatedDocuments, { ...options, title: "pile", _LODdisable: true, });
+                    Doc.pileup(pile, generatedDocuments);
+                    addDocument(pile);
+                } else {
+                    generatedDocuments.forEach(addDocument);
+                }
+                completed?.();
             } else {
                 if (text && !text.includes("https://")) {
                     addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d8e73a820..ab8174ba1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -337,31 +337,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
-
-        let w = 0, h = 0;
-        selected.forEach((d, i) => {
-            Doc.iconify(d);
-            w = Math.max(d[WidthSym](), w);
-            h = Math.max(d[HeightSym](), h);
-        });
-        selected.forEach((d, i) => {
-            this.props.removeDocument(d);
-            d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2;
-            d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2;
-            d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-        });
+        selected.forEach(d => this.props.removeDocument(d));
         const newCollection = this.getCollection(selected, false);
-        newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
-        newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
-        newCollection._width = newCollection._height = 110;
-        //newCollection.borderRounding = "40px";
-        newCollection.jitterRotation = 10;
-        newCollection._backgroundColor = "yellow";
+        Doc.pileup(newCollection, selected);
         this.props.addDocument(newCollection);
         this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-        Doc.pileup(newCollection);
     }
 
     @action
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index cf6a7ba5b..e189f5814 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -223,8 +223,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
             return url.href;//Why is this here
         }
         const ext = path.extname(url.href);
-        const suffix = this.props.renderDepth < 1 ? "_o" : this._curSuffix;
-        return url.href.replace(ext, suffix + ext);
+        this._curSuffix = this.props.renderDepth < 1 ? "_o" : this.layoutDoc[WidthSym]() < 100 ? "_s" : "_m";
+        return url.href.replace(ext, this._curSuffix + ext);
     }
 
     @observable _smallRetryCount = 1;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index cc929c040..52c3ce141 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -976,7 +976,26 @@ export namespace Doc {
         if (layoutKey && layoutKey !== "layout") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(newCollection: Doc) {
+    export function pileup(newCollection: Doc, selected: Doc[]) {
+        let w = 0, h = 0;
+        selected.forEach((d, i) => {
+            Doc.iconify(d);
+            w = Math.max(d[WidthSym](), w);
+            h = Math.max(d[HeightSym](), h);
+        });
+        h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away.  so this is a fallback hack to try to do something reasonable
+        selected.forEach((d, i) => {
+            d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2;
+            d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2;
+            d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+        });
+        newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+        newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+        newCollection._width = newCollection._height = 110;
+        //newCollection.borderRounding = "40px";
+        newCollection.jitterRotation = 10;
+        newCollection._backgroundColor = "gray";
+
         newCollection._layoutEngine = "pass";
         newCollection.overflow = "visible";
         const script = "if (self._layoutEngine === 'starburst') {" +
-- 
cgit v1.2.3-70-g09d2


From efd038bd12de35abc80141b130be523e3fea077a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 03:42:51 -0400
Subject: hopefully final cleanup of pile up collection view.   added doubleTap
 support in setupMoveUpEvents

---
 src/Utils.ts                                       | 12 ++-
 src/client/documents/Documents.ts                  |  4 +
 .../views/collections/CollectionPileView.scss      |  8 ++
 .../views/collections/CollectionPileView.tsx       | 97 ++++++++++++++++++++++
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../views/collections/CollectionTimeView.tsx       |  3 +-
 src/client/views/collections/CollectionView.tsx    |  5 +-
 .../CollectionFreeFormLayoutEngines.tsx            | 95 ++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 19 +++--
 .../collections/collectionFreeForm/MarqueeView.tsx | 16 ++--
 src/new_fields/Doc.ts                              | 35 ++------
 11 files changed, 189 insertions(+), 109 deletions(-)
 create mode 100644 src/client/views/collections/CollectionPileView.scss
 create mode 100644 src/client/views/collections/CollectionPileView.tsx

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 9acdc8731..ad12c68a1 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -504,8 +504,9 @@ export function setupMoveUpEvents(
     e: React.PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
     upEvent: (e: PointerEvent) => void,
-    clickEvent: (e: PointerEvent) => void,
-    stopPropagation: boolean = true
+    clickEvent: (e: PointerEvent, doubleTap?: boolean) => void,
+    stopPropagation: boolean = true,
+    stopMovePropagation: boolean = true
 ) {
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
@@ -520,12 +521,15 @@ export function setupMoveUpEvents(
         }
         (target as any)._lastX = e.clientX;
         (target as any)._lastY = e.clientY;
-        e.stopPropagation();
+        stopMovePropagation && e.stopPropagation();
     };
+    (target as any)._doubleTap = false;
     const _upEvent = (e: PointerEvent): void => {
+        (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300);
+        (target as any)._lastTap = Date.now();
         upEvent(e);
         if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) {
-            clickEvent(e);
+            clickEvent(e, (target as any)._doubleTap);
         }
         document.removeEventListener("pointermove", _moveEvent);
         document.removeEventListener("pointerup", _upEvent);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 578d337d6..1268c97b0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -612,6 +612,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id);
         }
 
+        export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Pile }, id);
+        }
+
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
         }
diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss
new file mode 100644
index 000000000..ac874b663
--- /dev/null
+++ b/src/client/views/collections/CollectionPileView.scss
@@ -0,0 +1,8 @@
+.collectionPileView {
+    display: flex;
+    flex-direction: row;
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    overflow: visible;
+}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
new file mode 100644
index 000000000..511efe0a7
--- /dev/null
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -0,0 +1,97 @@
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionPileView.scss";
+import React = require("react");
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+
+@observer
+export class CollectionPileView extends CollectionSubView(doc => doc) {
+    _lastTap = 0;
+    _doubleTap: boolean | undefined = false;
+    _originalChrome: string = "";
+    @observable _contentsActive = true;
+    @observable _layoutEngine = "pass";
+    @observable _collapsed: boolean = false;
+    @observable _childClickedScript: Opt<ScriptField>;
+    componentDidMount() {
+        this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
+        this.layoutDoc._chromeStatus = "disabled";
+        this.layoutDoc.hideFilterView = true;
+    }
+    componentWillUnmount() {
+        this.layoutDoc.hideFilterView = false;
+        this.layoutDoc._chromeStatus = this._originalChrome;
+    }
+
+    layoutEngine = () => this._layoutEngine;
+
+    @computed get contents() {
+        return <div className="collectionPileView-innards" style={{ width: "100%", pointerEvents: this._contentsActive && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none" }} >
+            <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
+        </div>;
+    }
+
+    specificMenu = (e: React.MouseEvent) => {
+        const layoutItems: ContextMenuProps[] = [];
+        const doc = this.props.Document;
+
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
+    }
+
+    toggleStarburst = action(() => {
+        if (this._layoutEngine === 'starburst') {
+            const defaultSize = 110;
+            this.layoutDoc.overflow = undefined;
+            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
+            this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
+            this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
+            this._layoutEngine = 'pass';
+        } else {
+            const defaultSize = 25;
+            this.layoutDoc.overflow = 'visible';
+            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+            !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
+            if (this._layoutEngine === 'pass') {
+                this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
+                this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
+                this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
+                this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
+            }
+            this.layoutDoc._width = this.layoutDoc._height = defaultSize;
+            this._layoutEngine = 'starburst';
+        }
+    });
+
+    pointerDown = (e: React.PointerEvent) => {
+        // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false, false); // this sets _doubleTap
+    }
+
+    onClick = (e: React.MouseEvent) => {
+        if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) {
+            SelectionManager.DeselectAll();
+            this.toggleStarburst();
+            e.stopPropagation();
+        } else if (this.layoutEngine() === "pass") {
+            runInAction(() => this._contentsActive = false);
+            setTimeout(action(() => this._contentsActive = true), 300);
+        }
+    }
+
+    render() {
+
+        return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown}
+            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            {this.contents}
+        </div>;
+    }
+}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 78ba3d6e6..c530b24fb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -400,9 +400,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             if (generatedDocuments.length) {
                 const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
                 if (set) {
-                    const pile = Docs.Create.FreeformDocument(generatedDocuments, { ...options, title: "pile", _LODdisable: true, });
-                    Doc.pileup(pile, generatedDocuments);
-                    addDocument(pile);
+                    addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!));
                 } else {
                     generatedDocuments.forEach(addDocument);
                 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 63498cea1..045134225 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -19,7 +19,6 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { DocumentView } from "../nodes/DocumentView";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -84,7 +83,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     }
 
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
             <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 47e79f088..f82c6d8a6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -46,6 +46,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import CollectionMapView from './CollectionMapView';
 import { Transform } from 'prosemirror-transform';
+import { CollectionPileView } from './CollectionPileView';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -67,7 +68,8 @@ export enum CollectionViewType {
     Carousel = "carousel",
     Linear = "linear",
     Staff = "staff",
-    Map = "map"
+    Map = "map",
+    Pile = "pileup"
 }
 
 export interface CollectionRenderProps {
@@ -169,6 +171,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />);
             case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />);
             case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
+            case CollectionViewType.Pile: { return (<CollectionPileView key="collview" {...props} />); }
             case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d8bd18ec7..9ca747113 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -13,10 +13,11 @@ import { listSpec } from "../../../../new_fields/Schema";
 
 export interface ViewDefBounds {
     type: string;
-    text?: string;
+    payload: any;
     x: number;
     y: number;
     z?: number;
+    text?: string;
     zIndex?: number;
     width?: number;
     height?: number;
@@ -24,14 +25,13 @@ export interface ViewDefBounds {
     fontSize?: number;
     highlight?: boolean;
     color?: string;
-    payload: any;
     replica?: string;
     pair?: { layout: Doc, data?: Doc };
 }
 
 export interface PoolData {
-    x?: number;
-    y?: number;
+    x: number;
+    y: number;
     z?: number;
     zIndex?: number;
     width?: number;
@@ -40,7 +40,7 @@ export interface PoolData {
     transition?: string;
     highlight?: boolean;
     replica?: string;
-    pair: { layout: Doc, data?: Doc }
+    pair: { layout: Doc, data?: Doc };
 }
 
 export interface ViewDefResult {
@@ -84,96 +84,88 @@ interface PivotColumn {
 export function computerPassLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     childPairs.forEach(({ layout, data }, i) => {
         docMap.set(layout[Id], {
-            type: "doc",
             x: NumCast(layout.x),
             y: NumCast(layout.y),
             width: layout[WidthSym](),
             height: layout[HeightSym](),
-            payload: undefined,
             pair: { layout, data },
             replica: ""
         });
     });
-    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
 }
 
 export function computerStarburstLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
     const docScale = NumCast(pivotDoc._starburstDocScale);
     const docSize = docScale * 100; // assume a icon sized at 100
     const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
-    childPairs.forEach((pair, i) => {
+    childPairs.forEach(({ layout, data }, i) => {
         const deg = i / childPairs.length * Math.PI * 2;
-        docMap.set(pair.layout[Id], {
-            type: "doc",
-            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * pair.layout[WidthSym]() / 2,
-            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * pair.layout[HeightSym]() / 2,
-            width: docScale * pair.layout[WidthSym](),
-            height: docScale * pair.layout[HeightSym](),
-            payload: undefined,
+        docMap.set(layout[Id], {
+            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2,
+            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2,
+            width: docScale * layout[WidthSym](),
+            height: docScale * layout[HeightSym](),
+            pair: { layout, data },
             replica: ""
         });
     });
-    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
 }
 
 
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
-    for (const doc of filterDocs) {
-        const lval = Cast(doc[pivotFieldKey], listSpec("string"), null);
-        const val = Field.toString(doc[pivotFieldKey] as Field);
+    childPairs.map(pair => {
+        const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+        const val = Field.toString(pair.layout[pivotFieldKey] as Field);
         if (lval) {
             lval.forEach((val, i) => {
                 !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
-                pivotColumnGroups.get(val)!.docs.push(doc);
+                pivotColumnGroups.get(val)!.docs.push(pair.layout);
                 pivotColumnGroups.get(val)!.replicas.push(i.toString());
             });
         } else if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
-            pivotColumnGroups.get(val)!.docs.push(doc);
+            pivotColumnGroups.get(val)!.docs.push(pair.layout);
             pivotColumnGroups.get(val)!.replicas.push("");
         } else {
-            docMap.set(doc[Id], {
-                type: "doc",
+            docMap.set(pair.layout[Id], {
                 x: 0,
                 y: 0,
                 zIndex: -99,
                 width: 0,
                 height: 0,
-                payload: undefined,
-                pair: { layout: doc },
+                pair,
                 replica: ""
-            })
+            });
         }
-    }
+    });
     let nonNumbers = 0;
     childPairs.map(pair => {
         const num = toNumber(pair.layout[pivotFieldKey]);
@@ -252,12 +244,10 @@ export function computePivotLayout(
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc[Id] + (val.replicas || ""), {
-                type: "doc",
                 x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
-                payload: undefined,
                 pair: { layout: doc },
                 replica: val.replicas[i]
             });
@@ -273,7 +263,7 @@ export function computePivotLayout(
     const dividers = sortedPivotKeys.map((key, i) =>
         ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
-    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
 
 function toNumber(val: FieldResult<Field>) {
@@ -283,14 +273,13 @@ function toNumber(val: FieldResult<Field>) {
 export function computeTimelineLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     const groupNames: ViewDefBounds[] = [];
     const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
     const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
@@ -306,11 +295,11 @@ export function computeTimelineLayout(
 
     let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq;
     let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
-    filterDocs.map(doc => {
-        const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
+    childPairs.forEach(pair => {
+        const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
         if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
             !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
-            pivotDateGroups.get(num)!.push(doc);
+            pivotDateGroups.get(num)!.push(pair.layout);
             minTime = Math.min(num, minTime);
             maxTime = Math.max(num, maxTime);
         }
@@ -369,7 +358,7 @@ export function computeTimelineLayout(
     }
 
     const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
-    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
         keyDocs.forEach(doc => {
@@ -382,13 +371,11 @@ export function computeTimelineLayout(
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc[Id], {
-                type: "doc",
                 x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
                 zIndex: (curTime === key ? 1000 : zind++),
                 highlight: curTime === key,
                 width: wid / (Math.max(stack, 1)),
                 height: hgt / (Math.max(stack, 1)),
-                payload: undefined,
                 pair: { layout: doc },
                 replica: ""
             });
@@ -397,12 +384,19 @@ export function computeTimelineLayout(
     }
 }
 
-function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<string, ViewDefBounds>,
-    poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
-    extraDocs: { data?: Doc, layout: Doc }[]): ViewDefResult[] {
+function normalizeResults(
+    panelDim: number[],
+    fontHeight: number,
+    docMap: Map<string, PoolData>,
+    poolData: Map<string, PoolData>,
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    groupNames: ViewDefBounds[],
+    minWidth: number,
+    extras: ViewDefBounds[]
+): ViewDefResult[] {
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
-    const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0);
+    const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
@@ -420,12 +414,11 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: {
                 zIndex: newPosRaw.zIndex,
                 width: (newPosRaw.width || 0) * scale,
                 height: newPosRaw.height! * scale,
-                pair: ele[1].pair!
+                pair: ele[1].pair
             };
             poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
         }
     });
-    extraDocs.map(ed => poolData.set(ed.layout[Id], { x: 0, y: 0, zIndex: -99, pair: ed }));
 
     return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
         type: gname.type,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5555d9e84..af9d3c5be 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -902,7 +902,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
-            return { ...result, pair: params.pair, transition: "transform 1s" };
+            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, z, color, zIndex } = params.pair.layout;
@@ -954,12 +954,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         engine: (
             poolData: Map<string, PoolData>,
             pivotDoc: Doc,
-            filterDocs: Doc[],
             childPairs: { layout: Doc, data?: Doc }[],
             panelDim: number[],
-            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) {
-        return engine(poolData, this.props.Document, this.childDocs,
-            this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]
+    ) {
+        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -977,7 +976,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get doInternalLayoutComputation() {
         const newPool = new Map<string, PoolData>();
-        const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
+        const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
         switch (engine) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
@@ -1001,6 +1000,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements: ViewDefResult[] = computedElementData.slice();
+        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView
@@ -1009,8 +1009,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.viewDefDivClick ? false : undefined}
-                    jitterRotation={NumCast(this.props.Document.jitterRotation)}
+                    pointerEvents={this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true)) ?
+                        false : undefined}
+                    jitterRotation={NumCast(this.props.Document._jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
@@ -1082,7 +1083,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
         optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ab8174ba1..cd8166309 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,12 +1,12 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc";
 import { InkField, InkData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
 import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
 import { Utils } from "../../../../Utils";
-import { Docs, DocUtils } from "../../../documents/Documents";
+import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch } from "../../../util/UndoManager";
@@ -310,11 +310,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.hideMarquee();
     }
 
-    getCollection = (selected: Doc[], asTemplate: boolean, isBackground?: boolean) => {
+    getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => {
         const bounds = this.Bounds;
         // const inkData = this.ink ? this.ink.inkData : undefined;
-        const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
-        const newCollection = creator(selected, {
+        const newCollection = (creator || Docs.Create.FreeformDocument)(selected, {
             x: bounds.left,
             y: bounds.top,
             _panX: 0,
@@ -338,8 +337,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
         selected.forEach(d => this.props.removeDocument(d));
-        const newCollection = this.getCollection(selected, false);
-        Doc.pileup(newCollection, selected);
+        const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
         this.props.addDocument(newCollection);
         this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -359,7 +357,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 return d;
             });
         }
-        const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t");
+        const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
         this.props.addDocument(newCollection);
         this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -470,7 +468,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     }
     @action
     background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
-        const newCollection = this.getCollection([], false, true);
+        const newCollection = this.getCollection([], undefined, true);
         this.props.addDocument(newCollection);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 18ffc38f5..337274774 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -976,7 +976,8 @@ export namespace Doc {
         if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(newCollection: Doc, selected: Doc[]) {
+    export function pileup(selected: Doc[], x: number, y: number) {
+        const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
         let w = 0, h = 0;
         selected.forEach((d, i) => {
             Doc.iconify(d);
@@ -993,12 +994,10 @@ export namespace Doc {
         newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
         newCollection._width = newCollection._height = 110;
         //newCollection.borderRounding = "40px";
-        newCollection.jitterRotation = 10;
+        newCollection._jitterRotation = 10;
         newCollection._backgroundColor = "gray";
-
-        newCollection._layoutEngine = "pass";
         newCollection.overflow = "visible";
-        newCollection.onClick = ScriptField.MakeScript("toggleStarburst(self, this)", { self: Doc.name, this: Doc.name });
+        return newCollection;
     }
 
 
@@ -1055,28 +1054,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     return docs.length ? new List(docs) : prevValue;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); });
-Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
-Scripting.addGlobal(function toggleStarburst(self: Doc, tLayout: Doc) {
-    if (self._layoutEngine === 'starburst') {
-        const defaultSize = 110;
-        tLayout.overflow = undefined;
-        self.x = NumCast(self.x) + tLayout[WidthSym]() / 2 - NumCast(tLayout._starburstPileWidth, defaultSize) / 2;
-        self.y = NumCast(self.y) + tLayout[HeightSym]() / 2 - NumCast(tLayout._starburstPileHeight, defaultSize) / 2;
-        tLayout._width = NumCast(tLayout._starburstPileWidth, defaultSize);
-        tLayout._height = NumCast(tLayout._starburstPileHeight, defaultSize);
-        self._layoutEngine = 'pass';
-    } else {
-        const defaultSize = 25;
-        tLayout.overflow = 'visible';
-        !tLayout._starburstRadius && (tLayout._starburstRadius = 500);
-        !tLayout._starburstDocScale && (tLayout._starburstDocScale = 2.5);
-        if (self._layoutEngine === 'pass') {
-            self.x = NumCast(self.x) + tLayout[WidthSym]() / 2 - defaultSize / 2;
-            self.y = NumCast(self.y) + tLayout[HeightSym]() / 2 - defaultSize / 2;
-            tLayout._starburstPileWidth = tLayout[WidthSym]();
-            tLayout._starburstPileHeight = tLayout[HeightSym]();
-        }
-        tLayout._width = tLayout._height = defaultSize;
-        self._layoutEngine = 'starburst';
-    }
-});
\ No newline at end of file
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 627adb92b604e117694e26af65afdc19920ac509 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 21:26:28 -0400
Subject: added internalPreDrop to set dropActions.  made linksView turn off
 during dragging.

---
 src/client/util/DragManager.ts                     | 22 ++++++++++++++++++++--
 .../views/collections/CollectionPileView.tsx       |  2 ++
 src/client/views/collections/CollectionSubView.tsx |  7 ++++++-
 .../CollectionFreeFormLinksView.tsx                |  2 +-
 4 files changed, 29 insertions(+), 4 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 6bb44f7d6..6a55f4a00 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -179,7 +179,8 @@ export namespace DragManager {
 
     export function MakeDropTarget(
         element: HTMLElement,
-        dropFunc: (e: Event, de: DropEvent) => void
+        dropFunc: (e: Event, de: DropEvent) => void,
+        preDropFunc?: (e: Event, de: DropEvent) => void
     ): DragDropDisposer {
         if ("canDrop" in element.dataset) {
             throw new Error(
@@ -187,10 +188,13 @@ export namespace DragManager {
             );
         }
         element.dataset.canDrop = "true";
-        const handler = (e: Event) => { dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
+        const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+        const preDropHandler = (e: Event) => preDropFunc?.(e, (e as CustomEvent<DropEvent>).detail);
         element.addEventListener("dashOnDrop", handler);
+        preDropFunc && element.addEventListener("dashPreDrop", preDropHandler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
+            preDropFunc && element.removeEventListener("dashPreDrop", preDropHandler);
             delete element.dataset.canDrop;
         };
     }
@@ -425,6 +429,20 @@ export namespace DragManager {
         });
         if (target) {
             const complete = new DragCompleteEvent(false, dragData);
+            target.dispatchEvent(
+                new CustomEvent<DropEvent>("dashPreDrop", {
+                    bubbles: true,
+                    detail: {
+                        x: e.x,
+                        y: e.y,
+                        complete: complete,
+                        shiftKey: e.shiftKey,
+                        altKey: e.altKey,
+                        metaKey: e.metaKey,
+                        ctrlKey: e.ctrlKey
+                    }
+                })
+            );
             finishDrag?.(complete);
             target.dispatchEvent(
                 new CustomEvent<DropEvent>("dashOnDrop", {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 410b16ec7..3bbfcc4d7 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -78,6 +78,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
         let dist = 0;
+        SelectionManager.SetIsDragging(true);
         // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
         setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
             if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
@@ -97,6 +98,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         }, () => {
             this._undoBatch?.end();
             this._undoBatch = undefined;
+            SelectionManager.SetIsDragging(false);
             if (!this.childDocs.length) {
                 this.props.ContainingCollectionView?.removeDocument(this.props.Document);
             }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c530b24fb..60cc1bd49 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -64,7 +64,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             this.multiTouchDisposer?.();
             if (ele) {
                 this._mainCont = ele;
-                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.onInternalPreDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
@@ -208,6 +208,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
         }
 
+        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent): boolean {
+            // de.complete.docDragData!.dropAction = "alias";  bcz: for example, you can convert all drops to aliases here
+            return true;
+        }
+
         @undoBatch
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 702b02a20..4b5e977df 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -36,7 +36,7 @@ export class CollectionFreeFormLinksView extends React.Component {
     }
 
     render() {
-        return <div className="collectionfreeformlinksview-container">
+        return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
             <svg className="collectionfreeformlinksview-svgCanvas">
                 {this.uniqueConnections}
             </svg>
-- 
cgit v1.2.3-70-g09d2


From f1ac48c19b4683668b0df91bd4ad68690a069029 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 21:43:21 -0400
Subject: added a targetDropAction field to specify the drop action from the
 target's side

---
 src/client/util/DragManager.ts                            | 15 ++++++++++-----
 src/client/views/collections/CollectionCarouselView.tsx   |  2 +-
 src/client/views/collections/CollectionLinearView.tsx     |  2 +-
 .../views/collections/CollectionSchemaMovableTableHOC.tsx |  2 +-
 src/client/views/collections/CollectionSubView.tsx        |  9 ++-------
 src/client/views/collections/CollectionTreeView.tsx       |  4 ++--
 src/client/views/collections/CollectionViewChromes.tsx    |  4 ++--
 src/client/views/nodes/DocumentView.tsx                   |  4 ++--
 src/client/views/nodes/FormattedTextBox.tsx               |  2 +-
 src/client/views/nodes/ImageBox.tsx                       |  2 +-
 src/client/views/nodes/LabelBox.tsx                       |  2 +-
 src/new_fields/documentSchemas.ts                         |  3 ++-
 12 files changed, 26 insertions(+), 25 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 6a55f4a00..577366cd4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,5 +1,5 @@
 import { Doc, Field, DocListCast } from "../../new_fields/Doc";
-import { Cast, ScriptCast } from "../../new_fields/Types";
+import { Cast, ScriptCast, StrCast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
@@ -180,7 +180,7 @@ export namespace DragManager {
     export function MakeDropTarget(
         element: HTMLElement,
         dropFunc: (e: Event, de: DropEvent) => void,
-        preDropFunc?: (e: Event, de: DropEvent) => void
+        doc?:Doc
     ): DragDropDisposer {
         if ("canDrop" in element.dataset) {
             throw new Error(
@@ -189,12 +189,17 @@ export namespace DragManager {
         }
         element.dataset.canDrop = "true";
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
-        const preDropHandler = (e: Event) => preDropFunc?.(e, (e as CustomEvent<DropEvent>).detail);
+        const preDropHandler = (e:Event) => {
+            const de = (e as CustomEvent<DropEvent>).detail;
+            if (de.complete.docDragData && doc?.targetDropAction) {
+                de.complete.docDragData!.dropAction = StrCast(doc.targetDropAction) as dropActionType;
+            }
+        }
         element.addEventListener("dashOnDrop", handler);
-        preDropFunc && element.addEventListener("dashPreDrop", preDropHandler);
+        doc && element.addEventListener("dashPreDrop", preDropHandler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
-            preDropFunc && element.removeEventListener("dashPreDrop", preDropHandler);
+            doc && element.removeEventListener("dashPreDrop", preDropHandler);
             delete element.dataset.canDrop;
         };
     }
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 9e7248db2..2453acddf 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -27,7 +27,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer?.();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
         }
     }
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index cb0206260..344dca23a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -64,7 +64,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
         }
     }
 
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 670d6dbb2..972714e34 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -54,7 +54,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
     }
 
     createColDropTarget = (ele: HTMLDivElement) => {
-        this._colDropDisposer && this._colDropDisposer();
+        this._colDropDisposer?.();
         if (ele) {
             this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
         }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 60cc1bd49..49abc6ee6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -11,7 +11,7 @@ import { Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
 import { FieldViewProps } from "../nodes/FieldView";
@@ -64,7 +64,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             this.multiTouchDisposer?.();
             if (ele) {
                 this._mainCont = ele;
-                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.onInternalPreDrop.bind(this));
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
@@ -208,11 +208,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
         }
 
-        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent): boolean {
-            // de.complete.docDragData!.dropAction = "alias";  bcz: for example, you can convert all drops to aliases here
-            return true;
-        }
-
         @undoBatch
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ca2004b79..8c5a4538e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -143,7 +143,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer && this._treedropDisposer();
-        ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
+        ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)), this.props.document);
     }
 
     onPointerEnter = (e: React.PointerEvent): void => {
@@ -663,7 +663,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer?.();
         if (this._mainEle = ele) {
-            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document);
         }
     }
 
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 7315d2c4e..d26e3a38b 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -215,9 +215,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     private dropDisposer?: DragManager.DragDropDisposer;
     protected createDropTarget = (ele: HTMLDivElement) => {
-        this.dropDisposer && this.dropDisposer();
+        this.dropDisposer?.();
         if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document);
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7ae77fc65..2aee49ad3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -191,7 +191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @action
     componentDidMount() {
-        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document));
         this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
         this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
         // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
@@ -208,7 +208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.multiTouchDisposer?.();
         this.holdDisposer?.();
         if (this._mainCont.current) {
-            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
             this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
             this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
             this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d7cfbae9f..815b8b3ae 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -253,7 +253,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.ProseRef = ele;
         this.dropDisposer?.();
-        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
+        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 14b367868..08917d281 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -76,7 +76,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer && this._dropDisposer();
-        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 5c2fc3ffe..a41e4fac4 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -32,7 +32,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer?.();
         if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document);
         }
     }
 
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 61b185e5c..f24ad42b2 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -8,7 +8,8 @@ export const documentSchema = createSchema({
     layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
     layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
     title: "string",            // document title (can be on either data document or layout)
-    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
+    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+    targetDropAction: "string",  // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
     childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
     _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
     _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
-- 
cgit v1.2.3-70-g09d2


From 69de8c235a6580ac222ef3f5b31746f6bc144659 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Apr 2020 10:48:16 -0400
Subject: rearranged text files

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    |    2 +-
 src/client/documents/Documents.ts                  |    2 +-
 src/client/util/DashDocCommentView.tsx             |   95 --
 src/client/util/DashDocView.tsx                    |  269 ----
 src/client/util/DashFieldView.scss                 |   36 -
 src/client/util/DashFieldView.tsx                  |  211 ----
 src/client/util/FootnoteView.tsx                   |  162 ---
 src/client/util/ImageResizeView.tsx                |  138 ---
 src/client/util/ParagraphNodeSpec.ts               |  143 ---
 src/client/util/ProsemirrorExampleTransfer.ts      |  241 ----
 src/client/util/RichTextMenu.scss                  |  121 --
 src/client/util/RichTextMenu.tsx                   |  875 -------------
 src/client/util/RichTextRules.ts                   |    4 +-
 src/client/util/RichTextSchema.tsx                 |  718 -----------
 src/client/util/SummaryView.tsx                    |   81 --
 src/client/util/TooltipTextMenu.scss               |  372 ------
 src/client/util/marks_rts.ts                       |  296 -----
 src/client/util/nodes_rts.ts                       |  264 ----
 src/client/util/prosemirrorPatches.js              |  139 ---
 src/client/util/schema_rts.ts                      |   26 -
 src/client/views/DocumentButtonBar.tsx             |    2 +-
 src/client/views/InkingControl.tsx                 |    2 +-
 src/client/views/MainView.tsx                      |    2 +-
 .../views/collections/CollectionCarouselView.tsx   |    2 +-
 src/client/views/collections/CollectionSubView.tsx |    2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |    2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |    2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |    2 +-
 src/client/views/nodes/FormattedTextBox.scss       |  265 ----
 src/client/views/nodes/FormattedTextBox.tsx        | 1306 --------------------
 .../views/nodes/FormattedTextBoxComment.scss       |   33 -
 src/client/views/nodes/FormattedTextBoxComment.tsx |  236 ----
 .../nodes/formattedText/DashDocCommentView.tsx     |   95 ++
 .../views/nodes/formattedText/DashDocView.tsx      |  269 ++++
 .../views/nodes/formattedText/DashFieldView.scss   |   36 +
 .../views/nodes/formattedText/DashFieldView.tsx    |  211 ++++
 .../views/nodes/formattedText/FootnoteView.tsx     |  162 +++
 .../nodes/formattedText/FormattedTextBox.scss      |  265 ++++
 .../views/nodes/formattedText/FormattedTextBox.tsx | 1303 +++++++++++++++++++
 .../formattedText/FormattedTextBoxComment.scss     |   33 +
 .../formattedText/FormattedTextBoxComment.tsx      |  236 ++++
 .../views/nodes/formattedText/ImageResizeView.tsx  |  138 +++
 .../views/nodes/formattedText/ParagraphNodeSpec.ts |  143 +++
 .../formattedText/ProsemirrorExampleTransfer.ts    |  241 ++++
 .../views/nodes/formattedText/RichTextMenu.scss    |  121 ++
 .../views/nodes/formattedText/RichTextMenu.tsx     |  875 +++++++++++++
 .../views/nodes/formattedText/RichTextRules.ts     |  319 +++++
 .../views/nodes/formattedText/RichTextSchema.tsx   |  718 +++++++++++
 .../views/nodes/formattedText/SummaryView.tsx      |   81 ++
 .../views/nodes/formattedText/TooltipTextMenu.scss |  372 ++++++
 src/client/views/nodes/formattedText/marks_rts.ts  |  296 +++++
 src/client/views/nodes/formattedText/nodes_rts.ts  |  264 ++++
 .../nodes/formattedText/prosemirrorPatches.js      |  139 +++
 src/client/views/nodes/formattedText/schema_rts.ts |   26 +
 src/mobile/MobileInterface.tsx                     |    2 +-
 src/new_fields/RichTextUtils.ts                    |    4 +-
 .../authentication/models/current_user_utils.ts    |    2 +-
 57 files changed, 6359 insertions(+), 6043 deletions(-)
 delete mode 100644 src/client/util/DashDocCommentView.tsx
 delete mode 100644 src/client/util/DashDocView.tsx
 delete mode 100644 src/client/util/DashFieldView.scss
 delete mode 100644 src/client/util/DashFieldView.tsx
 delete mode 100644 src/client/util/FootnoteView.tsx
 delete mode 100644 src/client/util/ImageResizeView.tsx
 delete mode 100644 src/client/util/ParagraphNodeSpec.ts
 delete mode 100644 src/client/util/ProsemirrorExampleTransfer.ts
 delete mode 100644 src/client/util/RichTextMenu.scss
 delete mode 100644 src/client/util/RichTextMenu.tsx
 delete mode 100644 src/client/util/RichTextSchema.tsx
 delete mode 100644 src/client/util/SummaryView.tsx
 delete mode 100644 src/client/util/TooltipTextMenu.scss
 delete mode 100644 src/client/util/marks_rts.ts
 delete mode 100644 src/client/util/nodes_rts.ts
 delete mode 100644 src/client/util/prosemirrorPatches.js
 delete mode 100644 src/client/util/schema_rts.ts
 delete mode 100644 src/client/views/nodes/FormattedTextBox.scss
 delete mode 100644 src/client/views/nodes/FormattedTextBox.tsx
 delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.scss
 delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.tsx
 create mode 100644 src/client/views/nodes/formattedText/DashDocCommentView.tsx
 create mode 100644 src/client/views/nodes/formattedText/DashDocView.tsx
 create mode 100644 src/client/views/nodes/formattedText/DashFieldView.scss
 create mode 100644 src/client/views/nodes/formattedText/DashFieldView.tsx
 create mode 100644 src/client/views/nodes/formattedText/FootnoteView.tsx
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.scss
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.tsx
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
 create mode 100644 src/client/views/nodes/formattedText/ImageResizeView.tsx
 create mode 100644 src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
 create mode 100644 src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
 create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.scss
 create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.tsx
 create mode 100644 src/client/views/nodes/formattedText/RichTextRules.ts
 create mode 100644 src/client/views/nodes/formattedText/RichTextSchema.tsx
 create mode 100644 src/client/views/nodes/formattedText/SummaryView.tsx
 create mode 100644 src/client/views/nodes/formattedText/TooltipTextMenu.scss
 create mode 100644 src/client/views/nodes/formattedText/marks_rts.ts
 create mode 100644 src/client/views/nodes/formattedText/nodes_rts.ts
 create mode 100644 src/client/views/nodes/formattedText/prosemirrorPatches.js
 create mode 100644 src/client/views/nodes/formattedText/schema_rts.ts

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 8c0149a89..e3f801c46 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -10,7 +10,7 @@ import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/Share
 import { Utils } from "../../../Utils";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { Networking } from "../../Network";
-import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox";
 import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
 import Photos = require('googlephotos');
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c64916897..f96e3bcd1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,7 @@
 import { CollectionView } from "../views/collections/CollectionView";
 import { CollectionViewType } from "../views/collections/CollectionView";
 import { AudioBox } from "../views/nodes/AudioBox";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
 import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
diff --git a/src/client/util/DashDocCommentView.tsx b/src/client/util/DashDocCommentView.tsx
deleted file mode 100644
index e716fac53..000000000
--- a/src/client/util/DashDocCommentView.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { List } from "../../new_fields/List";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { listSpec } from "../../new_fields/Schema";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
-import { DocServer } from "../DocServer";
-
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-interface IDashDocCommentView {
-    node: any;
-    view: any;
-    getPos: any;
-}
-
-export class DashDocCommentView extends React.Component<IDashDocCommentView>{
-    constructor(props: IDashDocCommentView) {
-        super(props);
-    }
-
-    targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
-        for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) {
-            const m = this.props.view.state.doc.nodeAt(i);
-            if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) {
-                return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
-            }
-        }
-        const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" });
-        this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc));
-        setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
-        return undefined;
-    }
-
-    onPointerDownCollapse = (e: any) => e.stopPropagation();
-
-    onPointerUpCollapse = (e: any) => {
-        const target = this.targetNode();
-        if (target) {
-            const expand = target.hidden;
-            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
-            this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
-            setTimeout(() => {
-                expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
-                try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
-            }, 0);
-        }
-        e.stopPropagation();
-    }
-
-    onPointerEnterCollapse = (e: any) => {
-        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
-        e.preventDefault();
-        e.stopPropagation();
-    }
-
-    onPointerLeaveCollapse = (e: any) => {
-        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
-        e.preventDefault();
-        e.stopPropagation();
-    }
-
-    render() {
-
-        const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid;
-
-        return (
-            <span
-                className="formattedTextBox-inlineComment"
-                id={collapsedId}
-                onPointerDown={this.onPointerDownCollapse}
-                onPointerUp={this.onPointerUpCollapse}
-                onPointerEnter={this.onPointerEnterCollapse}
-                onPointerLeave={this.onPointerLeaveCollapse}
-            >
-
-            </span >
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/DashDocView.tsx b/src/client/util/DashDocView.tsx
deleted file mode 100644
index 39809187f..000000000
--- a/src/client/util/DashDocView.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import { IReactionDisposer, reaction } from "mobx";
-import { NodeSelection } from "prosemirror-state";
-import { Doc, HeightSym, WidthSym } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { Docs } from "../documents/Documents";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Transform } from "./Transform";
-import React = require("react");
-
-interface IDashDocView {
-    node: any;
-    view: any;
-    getPos: any;
-    tbox?: FormattedTextBox;
-    self: any;
-}
-
-export class DashDocView extends React.Component<IDashDocView> {
-
-    _dashDoc: Doc | undefined;
-    _reactionDisposer: IReactionDisposer | undefined;
-    _renderDisposer: IReactionDisposer | undefined;
-    _textBox: FormattedTextBox;
-    _finalLayout: any;
-    _resolvedDataDoc: any;
-
-
-    //    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-
-    constructor(props: IDashDocView) {
-        super(props);
-
-        const node = this.props.node;
-        this._textBox = this.props.tbox as FormattedTextBox;
-
-        const alias = node.attrs.alias;
-        const docid = node.attrs.docid || this._textBox.props.Document[Id];
-
-        DocServer.GetRefField(docid + alias).then(async dashDoc => {
-            if (!(dashDoc instanceof Doc)) {
-                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
-                    if (dashDocBase instanceof Doc) {
-                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
-                        aliasedDoc.layoutKey = "layout";
-                        node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
-                        this._dashDoc = aliasedDoc;
-                        //                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
-                    }
-                });
-            } else {
-                this._dashDoc = dashDoc;
-                //                self.doRender(dashDoc, removeDoc, node, view, getPos);
-            }
-        });
-
-        this.onPointerLeave = this.onPointerLeave.bind(this);
-        this.onPointerEnter = this.onPointerEnter.bind(this);
-        this.onKeyDown = this.onKeyDown.bind(this);
-        this.onKeyPress = this.onKeyPress.bind(this);
-        this.onKeyUp = this.onKeyUp.bind(this);
-        this.onWheel = this.onWheel.bind(this);
-    }
-    /* #region Internal functions */
-
-    removeDoc = () => {
-        const view = this.props.view;
-        const pos = this.props.getPos();
-        const ns = new NodeSelection(view.state.doc.resolve(pos));
-        view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
-        return true;
-    }
-
-    getDocTransform = () => {
-        const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement;
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement);
-        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
-    }
-    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
-
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
-
-    onKeyPress = (e: any) => {
-        e.stopPropagation();
-    }
-    onWheel = (e: any) => {
-        e.preventDefault();
-    }
-    onKeyUp = (e: any) => {
-        e.stopPropagation();
-    }
-    onKeyDown = (e: any) => {
-        e.stopPropagation();
-        if (e.key === "Tab" || e.key === "Enter") {
-            e.preventDefault();
-        }
-    }
-    onPointerLeave = () => {
-        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
-        if (ele) {
-            (ele as HTMLDivElement).style.backgroundColor = "";
-        }
-    }
-    onPointerEnter = () => {
-        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
-        if (ele) {
-            (ele as HTMLDivElement).style.backgroundColor = "orange";
-        }
-    }
-    /*endregion*/
-
-    componentWillMount = () => {
-        this._reactionDisposer?.();
-    }
-
-    componentDidUpdate = () => {
-
-        this._renderDisposer?.();
-        this._renderDisposer = reaction(() => {
-
-            const dashDoc = this._dashDoc as Doc;
-            const dashLayoutDoc = Doc.Layout(dashDoc);
-            const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey);
-
-            if (finalLayout) {
-                if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                    finalLayout.rootDocument = dashDoc.aliasOf;
-                }
-                const layoutKey = StrCast(finalLayout.layoutKey);
-                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
-                if (finalLayout !== dashDoc && finalKey) {
-                    const finalLayoutField = finalLayout[finalKey];
-                    if (finalLayoutField instanceof ObjectField) {
-                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
-                    }
-                }
-                this._finalLayout = finalLayout;
-                this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null);
-                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
-            }
-        },
-            (res) => {
-
-                if (res) {
-                    this._finalLayout = res.finalLayout;
-                    this._resolvedDataDoc = res.resolvedDataDoc;
-
-                    this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc),
-                }
-            },
-            { fireImmediately: true });
-
-    }
-
-    render() {
-        // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
-
-        const node = this.props.node;
-        const view = this.props.view;
-        const getPos = this.props.getPos;
-
-        const spanStyle = {
-            width: this.props.node.props.width,
-            height: this.props.node.props.height,
-            position: 'absolute' as 'absolute',
-            display: 'inline-block'
-        };
-
-
-        const outerStyle = {
-            position: "relative" as "relative",
-            textIndent: "0",
-            border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")),
-            width: this.props.node.props.width,
-            height: this.props.node.props.height,
-            display: this.props.node.props.hidden ? "none" : "inline-block",
-            float: this.props.node.props.float,
-        };
-
-        const dashDoc = this._dashDoc as Doc;
-        const self = this;
-        const dashLayoutDoc = Doc.Layout(dashDoc);
-        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
-        const resolvedDataDoc = this._resolvedDataDoc; //Added this
-
-        if (!finalLayout) {
-            return <div></div>;
-            // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
-        } else {
-
-            this._reactionDisposer?.();
-            this._reactionDisposer = reaction(() =>
-                ({
-                    dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()],
-                    color: finalLayout.color
-                }),
-                ({ 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().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
-                }, { fireImmediately: true });
-
-            if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
-                try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
-                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
-                } catch (e) {
-                    console.log(e);
-                }
-            }
-
-
-            //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
-            //    ReactDOM.unmountComponentAtNode(this._dashSpan);
-
-            return (
-                <span id="dash-document-view-outer"
-                    className="outer"
-                    style={outerStyle}
-                >
-                    <div id="dashSpan"
-                        className="dash-span"
-                        style={spanStyle}
-                        onPointerLeave={this.onPointerLeave}
-                        onPointerEnter={this.onPointerEnter}
-                        onKeyDown={this.onKeyDown}
-                        onKeyPress={this.onKeyPress}
-                        onKeyUp={this.onKeyUp}
-                        onWheel={this.onWheel}
-                    >
-                        <DocumentView
-                            Document={finalLayout}
-                            DataDoc={resolvedDataDoc}
-                            LibraryPath={this._textBox.props.LibraryPath}
-                            fitToBox={BoolCast(dashDoc._fitToBox)}
-                            addDocument={returnFalse}
-                            rootSelected={this._textBox.props.isSelected}
-                            removeDocument={this.removeDoc}
-                            ScreenToLocalTransform={this.getDocTransform}
-                            addDocTab={this._textBox.props.addDocTab}
-                            pinToPres={returnFalse}
-                            renderDepth={self._textBox.props.renderDepth + 1}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
-                            PanelWidth={finalLayout[WidthSym]}
-                            PanelHeight={finalLayout[HeightSym]}
-                            focus={this.outerFocus}
-                            backgroundColor={returnEmptyString}
-                            parentActive={returnFalse}
-                            whenActiveChanged={returnFalse}
-                            bringToFront={emptyFunction}
-                            dontRegisterView={false}
-                            ContainingCollectionView={this._textBox.props.ContainingCollectionView}
-                            ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
-                            ContentScaling={this.contentScaling}
-                        />
-
-                    </div>
-                </span>
-            );
-
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/util/DashFieldView.scss b/src/client/util/DashFieldView.scss
deleted file mode 100644
index 35ff9c1e6..000000000
--- a/src/client/util/DashFieldView.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-.dashFieldView {
-    position: relative;
-    display: inline-block;
-
-    .dashFieldView-enumerables {
-        width: 10px;
-        height: 10px;
-        position: relative;
-        display: inline-block;
-        background: dimGray;
-    }
-    .dashFieldView-fieldCheck {
-        min-width: 12px;
-        position: relative;
-        display: inline-block;
-        background-color: rgba(155, 155, 155, 0.24);
-    }
-    .dashFieldView-labelSpan {
-        position: relative;
-        display: inline-block;
-        font-size: small;
-    }
-    .dashFieldView-fieldSpan {
-        min-width: 20px;
-        margin-left: 2px;
-        margin-right: 5px;
-        position: relative;
-        display: inline-block;
-        background-color: rgba(155, 155, 155, 0.24);
-        span {
-            min-width: 100%;
-            display: inline-block;
-        }
-    }
-}
-        
\ No newline at end of file
diff --git a/src/client/util/DashFieldView.tsx b/src/client/util/DashFieldView.tsx
deleted file mode 100644
index cf09e0132..000000000
--- a/src/client/util/DashFieldView.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
-import { Doc, DocListCast, Field } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
-import { listSpec } from "../../new_fields/Schema";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../new_fields/Types";
-import { DocServer } from "../DocServer";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import React = require("react");
-import * as ReactDOM from 'react-dom';
-import "./DashFieldView.scss";
-import { observer } from "mobx-react";
-
-
-export class DashFieldView {
-    _fieldWrapper: HTMLDivElement; // container for label and value
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this._fieldWrapper = document.createElement("div");
-        this._fieldWrapper.style.width = node.attrs.width;
-        this._fieldWrapper.style.height = node.attrs.height;
-        this._fieldWrapper.style.fontWeight = "bold";
-        this._fieldWrapper.style.position = "relative";
-        this._fieldWrapper.style.display = "inline-block";
-        this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
-        this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
-        this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
-        ReactDOM.render(<DashFieldViewInternal
-            fieldKey={node.attrs.fieldKey}
-            docid={node.attrs.docid}
-            width={node.attrs.width}
-            height={node.attrs.height}
-            view={view}
-            getPos={getPos}
-            tbox={tbox}
-        />, this._fieldWrapper);
-        (this as any).dom = this._fieldWrapper;
-    }
-    destroy() {
-        ReactDOM.unmountComponentAtNode(this._fieldWrapper);
-    }
-    selectNode() { }
-
-}
-interface IDashFieldViewInternal {
-    fieldKey: string;
-    docid: string;
-    view: any;
-    getPos: any;
-    tbox: FormattedTextBox;
-    width: number;
-    height: number;
-}
-
-@observer
-export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
-    _reactionDisposer: IReactionDisposer | undefined;
-    _textBoxDoc: Doc;
-    _fieldKey: string;
-    _fieldStringRef = React.createRef<HTMLSpanElement>();
-    @observable _showEnumerables: boolean = false;
-    @observable _dashDoc: Doc | undefined;
-
-    constructor(props: IDashFieldViewInternal) {
-        super(props);
-        this._fieldKey = this.props.fieldKey;
-        this._textBoxDoc = this.props.tbox.props.Document;
-
-        if (this.props.docid) {
-            DocServer.GetRefField(this.props.docid).
-                then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
-        } else {
-            this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc;
-        }
-    }
-    componentWillUnmount() {
-        this._reactionDisposer?.();
-    }
-
-    // set the display of the field's value (checkbox for booleans, span of text for strings)
-    @computed get fieldValueContent() {
-        if (this._dashDoc) {
-            const dashVal = this._dashDoc[this._fieldKey];
-            const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
-            const boolVal = Cast(fval, "boolean", null);
-            const strVal = Field.toString(fval as Field) || "";
-
-            // field value is a boolean, so use a checkbox or similar widget to display it
-            if (boolVal === true || boolVal === false) {
-                return <input
-                    className="dashFieldView-fieldCheck"
-                    type="checkbox" checked={boolVal}
-                    onChange={e => this._dashDoc![this._fieldKey] = e.target.checked}
-                />;
-            }
-            else // field value is a string, so display it as an editable span
-            {
-                // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
-                // use React events.  Essentially, React events occur after native events have been processed, so corresponding React events
-                // will never fire because Prosemirror has handled the native events.  So we add listeners for native events here.
-                return <span contentEditable={true} ref={r => {
-                    r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
-                    r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
-                    r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
-                }}>
-                    {strVal}
-                </span>
-            }
-        }
-    }
-
-    // we need to handle all key events on the input span or else they will propagate to prosemirror.
-    @action
-    fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
-        if (e.key === "Enter") {  // handle the enter key by "submitting" the current text to Dash's database. 
-            e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
-            this.updateText(span.textContent!, true);
-            e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
-        }
-        if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
-            if (window.getSelection) {
-                const range = document.createRange();
-                range.selectNodeContents(span);
-                window.getSelection()!.removeAllRanges();
-                window.getSelection()!.addRange(range);
-            }
-            e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
-        }
-        e.stopPropagation();  // we need to handle all events or else they will propagate to prosemirror.
-    }
-
-    @action
-    updateText = (nodeText: string, forceMatch: boolean) => {
-        this._showEnumerables = false;
-        if (nodeText) {
-            const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
-
-            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
-            // holds the different enumerated values for the field in the titles of its collected documents.
-            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
-            DocServer.GetRefField(this._fieldKey).then(options => {
-                let modText = "";
-                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
-                if (modText) {
-                    //  elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
-                    Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
-                    this._dashDoc![this._fieldKey] = modText;
-                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
-                else if (nodeText.startsWith(":=")) {
-                    this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
-                } else if (nodeText.startsWith("=:=")) {
-                    Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
-                } else {
-                    this._dashDoc![this._fieldKey] = newText;
-                }
-            });
-        }
-    }
-
-    // display a collection of all the enumerable values for this field
-    onPointerDownEnumerables = async (e: any) => {
-        e.stopPropagation();
-        const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
-        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
-    }
-
-
-    // clicking on the label creates a pivot view collection of all documents
-    // in the same collection.  The pivot field is the fieldKey of this label
-    onPointerDownLabelSpan = (e: any) => {
-        e.stopPropagation();
-        let container = this.props.tbox.props.ContainingCollectionView;
-        while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
-            container = container.props.ContainingCollectionView;
-        }
-        if (container) {
-            const alias = Doc.MakeAlias(container.props.Document);
-            alias.viewType = CollectionViewType.Time;
-            let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
-            if (!list) {
-                alias.schemaColumns = list = new List<SchemaHeaderField>();
-            }
-            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;
-            this.props.tbox.props.addDocTab(alias, "onRight");
-        }
-    }
-
-    render() {
-        return <div className="dashFieldView" style={{
-            width: this.props.width,
-            height: this.props.height,
-        }}>
-            <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
-                {this._fieldKey}
-            </span>
-
-            <div className="dashFieldView-fieldSpan">
-                {this.fieldValueContent}
-            </div>
-
-            {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
-
-        </div >;
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/FootnoteView.tsx b/src/client/util/FootnoteView.tsx
deleted file mode 100644
index ee21fb765..000000000
--- a/src/client/util/FootnoteView.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import { EditorView } from "prosemirror-view";
-import { EditorState } from "prosemirror-state";
-import { keymap } from "prosemirror-keymap";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { schema } from "./schema_rts";
-import { redo, undo } from "prosemirror-history";
-import { StepMap } from "prosemirror-transform";
-
-import React = require("react");
-
-interface IFootnoteView {
-    innerView: any;
-    outerView: any;
-    node: any;
-    dom: any;
-    getPos: any;
-}
-
-export class FootnoteView extends React.Component<IFootnoteView>  {
-    _innerView: any;
-    _node: any;
-
-    constructor(props: IFootnoteView) {
-        super(props);
-        const node = this.props.node;
-        const outerView = this.props.outerView;
-        const _innerView = this.props.innerView;
-        const getPos = this.props.getPos;
-    }
-
-    selectNode() {
-        const attrs = { ...this.props.node.attrs };
-        attrs.visibility = true;
-        this.dom.classList.add("ProseMirror-selectednode");
-        if (!this.props.innerView) this.open();
-    }
-
-    deselectNode() {
-        const attrs = { ...this.props.node.attrs };
-        attrs.visibility = false;
-        this.dom.classList.remove("ProseMirror-selectednode");
-        if (this.props.innerView) this.close();
-    }
-    open() {
-        // Append a tooltip to the outer node
-        const tooltip = this.dom.appendChild(document.createElement("div"));
-        tooltip.className = "footnote-tooltip";
-        // And put a sub-ProseMirror into that
-        this.props.innerView.defineProperty(new EditorView(tooltip, {
-            // You can use any node as an editor document
-            state: EditorState.create({
-                doc: this.props.node,
-                plugins: [keymap(baseKeymap),
-                keymap({
-                    "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch),
-                    "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch),
-                    "Mod-b": toggleMark(schema.marks.strong)
-                }),
-                    // new Plugin({
-                    //     view(newView) {
-                    //         // TODO -- make this work with RichTextMenu
-                    //         // return FormattedTextBox.getToolTip(newView);
-                    //     }
-                    // })
-                ],
-
-            }),
-            // This is the magic part
-            dispatchTransaction: this.dispatchInner.bind(this),
-            handleDOMEvents: {
-                pointerdown: ((view: any, e: PointerEvent) => {
-                    // Kludge to prevent issues due to the fact that the whole
-                    // footnote is node-selected (and thus DOM-selected) when
-                    // the parent editor is focused.
-                    e.stopPropagation();
-                    document.addEventListener("pointerup", this.ignore, true);
-                    if (this.props.outerView.hasFocus()) this.props.innerView.focus();
-                }) as any
-            }
-        }));
-        setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0);
-    }
-
-    ignore = (e: PointerEvent) => {
-        e.stopPropagation();
-        document.removeEventListener("pointerup", this.ignore, true);
-    }
-
-    dispatchInner(tr: any) {
-        const { state, transactions } = this.props.innerView.state.applyTransaction(tr);
-        this.props.innerView.updateState(state);
-
-        if (!tr.getMeta("fromOutside")) {
-            const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1);
-            for (const transaction of transactions) {
-                const steps = transaction.steps;
-                for (const step of steps) {
-                    outerTr.step(step.map(offsetMap));
-                }
-            }
-            if (outerTr.docChanged) this.props.outerView.dispatch(outerTr);
-        }
-    }
-    update(node: any) {
-        if (!node.sameMarkup(this.props.node)) return false;
-        this._node = node; //not sure
-        if (this.props.innerView) {
-            const state = this.props.innerView.state;
-            const start = node.content.findDiffStart(state.doc.content);
-            if (start !== null) {
-                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
-                const overlap = start - Math.min(endA, endB);
-                if (overlap > 0) { endA += overlap; endB += overlap; }
-                this.props.innerView.dispatch(
-                    state.tr
-                        .replace(start, endB, node.slice(start, endA))
-                        .setMeta("fromOutside", true));
-            }
-        }
-        return true;
-    }
-    onPointerUp = (e: any) => {
-        this.toggle(e);
-    }
-
-    toggle = (e: any) => {
-        e.preventDefault();
-        if (this.props.innerView) this.close();
-        else {
-            this.open();
-        }
-    }
-
-    close() {
-        this.props.innerView && this.props.innerView.destroy();
-        this._innerView = null;
-        this.dom.textContent = "";
-    }
-
-    destroy() {
-        if (this.props.innerView) this.close();
-    }
-
-    stopEvent(event: any) {
-        return this.props.innerView && this.props.innerView.dom.contains(event.target);
-    }
-
-    ignoreMutation() { return true; }
-
-
-    render() {
-        return (
-            <div
-                className="footnote"
-                onPointerUp={this.onPointerUp}>
-                <div className="footnote-tooltip" >
-
-                </div >
-            </div>
-        );
-    }
-}
diff --git a/src/client/util/ImageResizeView.tsx b/src/client/util/ImageResizeView.tsx
deleted file mode 100644
index 4f66475fb..000000000
--- a/src/client/util/ImageResizeView.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { NodeSelection } from "prosemirror-state";
-import { Doc } from "../../new_fields/Doc";
-import { DocServer } from "../DocServer";
-import { DocumentManager } from "./DocumentManager";
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-interface IImageResizeView {
-    node: any;
-    view: any;
-    getPos: any;
-    addDocTab: any;
-}
-
-export class ImageResizeView extends React.Component<IImageResizeView> {
-    constructor(props: IImageResizeView) {
-        super(props);
-    }
-
-    onClickImg = (e: any) => {
-        e.stopPropagation();
-        e.preventDefault();
-        if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) {
-            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2))));
-        }
-    }
-
-    onPointerDownImg = (e: any) => {
-        if (e.ctrlKey) {
-            e.preventDefault();
-            e.stopPropagation();
-            DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc =>
-                (linkDoc instanceof Doc) &&
-                DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document,
-                    document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false));
-        }
-    }
-
-    onPointerDownHandle = (e: any) => {
-        e.preventDefault();
-        e.stopPropagation();
-        const elementImage = document.getElementById("imageId") as HTMLElement;
-        const wid = Number(getComputedStyle(elementImage).width.replace(/px/, ""));
-        const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, ""));
-        const startX = e.pageX;
-        const startWidth = parseFloat(this.props.node.attrs.width);
-
-        const onpointermove = (e: any) => {
-            const elementOuter = document.getElementById("outerId") as HTMLElement;
-
-            const currentX = e.pageX;
-            const diffInPx = currentX - startX;
-            elementOuter.style.width = `${startWidth + diffInPx}`;
-            elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
-        };
-
-        const onpointerup = () => {
-            document.removeEventListener("pointermove", onpointermove);
-            document.removeEventListener("pointerup", onpointerup);
-            const pos = this.props.view.state.selection.from;
-            const elementOuter = document.getElementById("outerId") as HTMLElement;
-            this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height }));
-            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos))));
-        };
-
-        document.addEventListener("pointermove", onpointermove);
-        document.addEventListener("pointerup", onpointerup);
-    }
-
-    selectNode() {
-        const elementImage = document.getElementById("imageId") as HTMLElement;
-        const elementHandle = document.getElementById("handleId") as HTMLElement;
-
-        elementImage.classList.add("ProseMirror-selectednode");
-        elementHandle.style.display = "";
-    }
-
-    deselectNode() {
-        const elementImage = document.getElementById("imageId") as HTMLElement;
-        const elementHandle = document.getElementById("handleId") as HTMLElement;
-
-        elementImage.classList.remove("ProseMirror-selectednode");
-        elementHandle.style.display = "none";
-    }
-
-
-    render() {
-
-        const outerStyle = {
-            width: this.props.node.attrs.width,
-            height: this.props.node.attrs.height,
-            display: "inline-block",
-            overflow: "hidden",
-            float: this.props.node.attrs.float
-        };
-
-        const imageStyle = {
-            width: "100%",
-        };
-
-        const handleStyle = {
-            position: "absolute",
-            width: "20px",
-            heiht: "20px",
-            backgroundColor: "blue",
-            borderRadius: "15px",
-            display: "none",
-            bottom: "-10px",
-            right: "-10px"
-
-        };
-
-
-
-        return (
-            <div id="outer"
-                style={outerStyle}
-            >
-                <img
-                    id="imageId"
-                    style={imageStyle}
-                    src={this.props.node.src}
-                    onClick={this.onClickImg}
-                    onPointerDown={this.onPointerDownImg}
-
-                >
-                </img>
-                <span
-                    id="handleId"
-                    onPointerDown={this.onPointerDownHandle}
-                >
-
-                </span>
-            </div >
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts
deleted file mode 100644
index 0a3b68217..000000000
--- a/src/client/util/ParagraphNodeSpec.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import clamp from './clamp';
-import convertToCSSPTValue from './convertToCSSPTValue';
-import toCSSLineSpacing from './toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
-
-//import type { NodeSpec } from './Types';
-type NodeSpec = {
-    attrs?: { [key: string]: any },
-    content?: string,
-    draggable?: boolean,
-    group?: string,
-    inline?: boolean,
-    name?: string,
-    parseDOM?: Array<any>,
-    toDOM?: (node: any) => DOMOutputSpec,
-};
-
-// This assumes that every 36pt maps to one indent level.
-export const INDENT_MARGIN_PT_SIZE = 36;
-export const MIN_INDENT_LEVEL = 0;
-export const MAX_INDENT_LEVEL = 7;
-export const ATTRIBUTE_INDENT = 'data-indent';
-
-export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
-
-const ALIGN_PATTERN = /(left|right|center|justify)/;
-
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-const ParagraphNodeSpec: NodeSpec = {
-    attrs: {
-        align: { default: null },
-        color: { default: null },
-        id: { default: null },
-        indent: { default: null },
-        inset: { default: null },
-        lineSpacing: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingBottom: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingTop: { default: null },
-    },
-    content: 'inline*',
-    group: 'block',
-    parseDOM: [{ tag: 'p', getAttrs }],
-    toDOM,
-};
-
-function getAttrs(dom: HTMLElement): Object {
-    const {
-        lineHeight,
-        textAlign,
-        marginLeft,
-        paddingTop,
-        paddingBottom,
-    } = dom.style;
-
-    let align = dom.getAttribute('align') || textAlign || '';
-    align = ALIGN_PATTERN.test(align) ? align : "";
-
-    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
-
-    if (!indent && marginLeft) {
-        indent = convertMarginLeftToIndentValue(marginLeft);
-    }
-
-    indent = indent || MIN_INDENT_LEVEL;
-
-    const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null;
-
-    const id = dom.getAttribute('id') || '';
-    return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
-}
-
-function toDOM(node: Node): DOMOutputSpec {
-    const {
-        align,
-        indent,
-        inset,
-        lineSpacing,
-        paddingTop,
-        paddingBottom,
-        id,
-    } = node.attrs;
-    const attrs: { [key: string]: any } | null = {};
-
-    let style = '';
-    if (align && align !== 'left') {
-        style += `text-align: ${align};`;
-    }
-
-    if (lineSpacing) {
-        const cssLineSpacing = toCSSLineSpacing(lineSpacing);
-        style +=
-            `line-height: ${cssLineSpacing};` +
-            // This creates the local css variable `--czi-content-line-height`
-            // that its children may apply.
-            `--czi-content-line-height: ${cssLineSpacing}`;
-    }
-
-    if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) {
-        style += `padding-top: ${paddingTop};`;
-    }
-
-    if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) {
-        style += `padding-bottom: ${paddingBottom};`;
-    }
-
-    if (indent) {
-        style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
-    }
-
-    if (inset) {
-        style += `margin-left: ${inset}; margin-right: ${inset};`;
-    }
-
-    style && (attrs.style = style);
-
-    if (indent) {
-        attrs[ATTRIBUTE_INDENT] = String(indent);
-    }
-
-    if (id) {
-        attrs.id = id;
-    }
-
-    return ['p', attrs, 0];
-}
-
-export const toParagraphDOM = toDOM;
-export const getParagraphNodeAttrs = getAttrs;
-
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
-    const ptValue = convertToCSSPTValue(marginLeft);
-    return clamp(
-        MIN_INDENT_LEVEL,
-        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
-        MAX_INDENT_LEVEL
-    );
-}
-
-export default ParagraphNodeSpec;
\ No newline at end of file
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
deleted file mode 100644
index 356f20ce6..000000000
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ /dev/null
@@ -1,241 +0,0 @@
-import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { undoInputRule } from "prosemirror-inputrules";
-import { Schema } from "prosemirror-model";
-import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
-import { splitListItem, wrapInList, } from "prosemirror-schema-list";
-import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { SelectionManager } from "./SelectionManager";
-import { Docs } from "../documents/Documents";
-import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types";
-import { Doc } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Id } from "../../new_fields/FieldSymbols";
-
-const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
-
-export type KeyMap = { [key: string]: any };
-
-export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => {
-    let fontSize: number | undefined = undefined;
-    tx2.doc.descendants((node: any, offset: any, index: any) => {
-        if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
-            const path = (tx2.doc.resolve(offset) as any).path;
-            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
-            if (node.type === schema.nodes.ordered_list) depth++;
-            fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
-            const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
-            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
-        }
-    });
-    return tx2;
-};
-export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
-    const keys: { [key: string]: any } = {};
-
-    function bind(key: string, cmd: any) {
-        if (mapKeys) {
-            const mapped = mapKeys[key];
-            if (mapped === false) return;
-            if (mapped) key = mapped;
-        }
-        keys[key] = cmd;
-    }
-
-    bind("Mod-z", undo);
-    bind("Shift-Mod-z", redo);
-    bind("Backspace", undoInputRule);
-
-    !mac && bind("Mod-y", redo);
-
-    bind("Alt-ArrowUp", joinUp);
-    bind("Alt-ArrowDown", joinDown);
-    bind("Mod-BracketLeft", lift);
-    bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-        (document.activeElement as any).blur?.();
-        SelectionManager.DeselectAll();
-    });
-
-    bind("Mod-b", toggleMark(schema.marks.strong));
-    bind("Mod-B", toggleMark(schema.marks.strong));
-
-    bind("Mod-e", toggleMark(schema.marks.em));
-    bind("Mod-E", toggleMark(schema.marks.em));
-
-    bind("Mod-u", toggleMark(schema.marks.underline));
-    bind("Mod-U", toggleMark(schema.marks.underline));
-
-    bind("Mod-`", toggleMark(schema.marks.code));
-
-    bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
-
-    bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
-
-    bind("Ctrl->", wrapIn(schema.nodes.blockquote));
-
-    // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-    //     let newNode = schema.nodes.footnote.create({});
-    //     if (dispatch && state.selection.from === state.selection.to) {
-    //         let tr = state.tr;
-    //         tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
-    //         dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
-    //             tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
-    //                 tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
-    //         return true;
-    //     }
-    //     return false;
-    // });
-
-
-    const cmd = chainCommands(exitCode, (state, dispatch) => {
-        if (dispatch) {
-            dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
-            return true;
-        }
-        return false;
-    });
-    bind("Mod-Enter", cmd);
-    bind("Shift-Enter", cmd);
-    mac && bind("Ctrl-Enter", cmd);
-
-
-    bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
-
-    bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
-
-    for (let i = 1; i <= 6; i++) {
-        bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
-    }
-
-    const hr = schema.nodes.horizontal_rule;
-    bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
-        return true;
-    });
-
-    bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const ref = state.selection;
-        const range = ref.$from.blockRange(ref.$to);
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
-            const tx3 = updateBullets(tx2, schema);
-            marks && tx3.ensureMarks([...marks]);
-            marks && tx3.setStoredMarks([...marks]);
-            dispatch(tx3);
-        })) { // couldn't sink into an existing list, so wrap in a new one
-            const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
-            if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
-                const tx3 = updateBullets(tx2, schema);
-                // when promoting to a list, assume list will format things so don't copy the stored marks.
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-                dispatch(tx3);
-            })) {
-                console.log("bullet promote fail");
-            }
-        }
-    });
-
-    bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-
-        if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
-            const tx3 = updateBullets(tx2, schema);
-            marks && tx3.ensureMarks([...marks]);
-            marks && tx3.setStoredMarks([...marks]);
-            dispatch(tx3);
-        })) {
-            console.log("bullet demote fail");
-        }
-    });
-    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.rootDocument || layoutDoc;
-        if (originalDoc instanceof Doc) {
-            const layoutKey = StrCast(originalDoc.layoutKey);
-            const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
-                layoutKey,
-                _singleLine: BoolCast(originalDoc._singleLine),
-                x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
-            });
-            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
-                newDoc[layoutKey] = originalDoc[layoutKey];
-            }
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
-            props.addDocument(newDoc);
-        }
-    });
-
-    const splitMetadata = (marks: any, tx: Transaction) => {
-        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
-        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
-        return tx;
-    };
-    const addTextOnRight = (force: boolean) => {
-        const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.rootDocument || layoutDoc;
-        if (force || props.Document._singleLine) {
-            const layoutKey = StrCast(originalDoc.layoutKey);
-            const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
-                layoutKey,
-                _singleLine: BoolCast(originalDoc._singleLine),
-                x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
-            });
-            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
-                newDoc[layoutKey] = originalDoc[layoutKey];
-            }
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
-            props.addDocument(newDoc);
-            return true;
-        }
-        return false;
-    };
-    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-        return addTextOnRight(true);
-    });
-    bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-        if (addTextOnRight(false)) return true;
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        if (!splitListItem(schema.nodes.list_item)(state, dispatch)) {
-            if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
-                splitMetadata(marks, tx3);
-                if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
-                    dispatch(tx3);
-                }
-            })) {
-                return false;
-            }
-        }
-        return true;
-    });
-    bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        dispatch(splitMetadata(marks, state.tr));
-        return false;
-    });
-    bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
-            return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
-        });
-        const path = (state.doc.resolve(state.selection.from - 1) as any).path;
-        const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
-        const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator;
-        if (anchor >= 0) {
-            const textsel = TextSelection.create(state.doc, anchor, range!.end);
-            const text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
-            let whitespace = text.length - 1;
-            for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
-            if (text.endsWith(":")) {
-                dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
-                    addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
-            }
-        }
-        return false;
-    });
-
-
-    return keys;
-}
diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
deleted file mode 100644
index 43cc23ecd..000000000
--- a/src/client/util/RichTextMenu.scss
+++ /dev/null
@@ -1,121 +0,0 @@
-@import "../views/globalCssVariables";
-
-.button-dropdown-wrapper {
-    position: relative;
-
-    .dropdown-button {
-        width: 15px;
-        padding-left: 5px;
-        padding-right: 5px;
-    }
-
-    .dropdown-button-combined {
-        width: 50px;
-        display: flex;
-        justify-content: space-between;
-
-        svg:nth-child(2) {
-            margin-top: 2px;
-        }
-    }
-
-    .dropdown {
-        position: absolute;
-        top: 35px;
-        left: 0;
-        background-color: #323232;
-        color: $light-color-secondary;
-        border: 1px solid #4d4d4d;
-        border-radius: 0 6px 6px 6px;
-        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-        min-width: 150px;
-        padding: 5px;
-        font-size: 12px;
-        z-index: 10001;
-
-        button {
-            background-color: #323232;
-            border: 1px solid black;
-            border-radius: 1px;
-            padding: 6px;
-            margin: 5px 0;
-            font-size: 10px;
-
-            &:hover {
-                background-color: black;
-            }
-
-            &:last-child {
-                margin-bottom: 0;
-            }
-        }
-    }
-
-    input {
-        color: black;
-    }
-}
-
-.link-menu {
-    .divider {
-        background-color: white;
-        height: 1px;
-        width: 100%;
-    }
-}
-
-.color-preview-button {
-    .color-preview {
-        width: 100%;
-        height: 3px;
-        margin-top: 3px;
-    }
-}
-
-.color-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-
-    button.color-button {
-        width: 20px;
-        height: 20px;
-        border-radius: 15px !important;
-        margin: 3px;
-        border: 2px solid transparent !important;
-        padding: 3px;
-
-        &.active {
-            border: 2px solid white !important;
-        }
-    }
-}
-
-select {
-    background-color: #323232;
-    color: white;
-    border: 1px solid black;
-    // border-top: none;
-    // border-bottom: none;
-    font-size: 12px;
-    height: 100%;
-    margin-right: 3px;
-
-    &:focus,
-    &:hover {
-        background-color: black;
-    }
-
-    &::-ms-expand {
-        color: white;
-    }
-}
-
-.row-2 {
-    display: flex;
-    justify-content: space-between;
-
-    >div {
-        display: flex;
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
deleted file mode 100644
index 140635a1d..000000000
--- a/src/client/util/RichTextMenu.tsx
+++ /dev/null
@@ -1,875 +0,0 @@
-import React = require("react");
-import AntimodeMenu from "../views/AntimodeMenu";
-import { observable, action, } from "mobx";
-import { observer } from "mobx-react";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { schema } from "./schema_rts";
-import { EditorView } from "prosemirror-view";
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import { Cast, StrCast } from "../../new_fields/Types";
-import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { unimplementedFunction, Utils } from "../../Utils";
-import { wrapInList } from "prosemirror-schema-list";
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-import "./RichTextMenu.scss";
-import { DocServer } from "../DocServer";
-import { Doc } from "../../new_fields/Doc";
-import { SelectionManager } from "./SelectionManager";
-import { LinkManager } from "./LinkManager";
-const { toggleMark, setBlockType } = require("prosemirror-commands");
-
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
-
-@observer
-export default class RichTextMenu extends AntimodeMenu {
-    static Instance: RichTextMenu;
-    public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
-
-    private view?: EditorView;
-    public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
-
-    public _brushMap: Map<string, Set<Mark>> = new Map();
-    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
-    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
-    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
-    private fontColors: (string | undefined)[];
-    private highlightColors: (string | undefined)[];
-
-    @observable private collapsed: boolean = false;
-    @observable private boldActive: boolean = false;
-    @observable private italicsActive: boolean = false;
-    @observable private underlineActive: boolean = false;
-    @observable private strikethroughActive: boolean = false;
-    @observable private subscriptActive: boolean = false;
-    @observable private superscriptActive: boolean = false;
-
-    @observable private activeFontSize: string = "";
-    @observable private activeFontFamily: string = "";
-    @observable private activeListType: string = "";
-
-    @observable private brushIsEmpty: boolean = true;
-    @observable private brushMarks: Set<Mark> = new Set();
-    @observable private showBrushDropdown: boolean = false;
-
-    @observable private activeFontColor: string = "black";
-    @observable private showColorDropdown: boolean = false;
-
-    @observable private activeHighlightColor: string = "transparent";
-    @observable private showHighlightDropdown: boolean = false;
-
-    @observable private currentLink: string | undefined = "";
-    @observable private showLinkDropdown: boolean = false;
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-        RichTextMenu.Instance = this;
-        this._canFade = false;
-
-        this.fontSizeOptions = [
-            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
-        ];
-
-        this.fontFamilyOptions = [
-            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
-        ];
-
-        this.listTypeOptions = [
-            { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType },
-            { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
-        ];
-
-        this.fontColors = [
-            DarkPastelSchemaPalette.get("pink2"),
-            DarkPastelSchemaPalette.get("purple4"),
-            DarkPastelSchemaPalette.get("bluegreen1"),
-            DarkPastelSchemaPalette.get("yellow4"),
-            DarkPastelSchemaPalette.get("red2"),
-            DarkPastelSchemaPalette.get("bluegreen7"),
-            DarkPastelSchemaPalette.get("bluegreen5"),
-            DarkPastelSchemaPalette.get("orange1"),
-            "#757472",
-            "#000"
-        ];
-
-        this.highlightColors = [
-            PastelSchemaPalette.get("pink2"),
-            PastelSchemaPalette.get("purple4"),
-            PastelSchemaPalette.get("bluegreen1"),
-            PastelSchemaPalette.get("yellow4"),
-            PastelSchemaPalette.get("red2"),
-            PastelSchemaPalette.get("bluegreen7"),
-            PastelSchemaPalette.get("bluegreen5"),
-            PastelSchemaPalette.get("orange1"),
-            "white",
-            "transparent"
-        ];
-    }
-
-    @action
-    changeView(view: EditorView) {
-        this.view = view;
-    }
-
-    update(view: EditorView, lastState: EditorState | undefined) {
-        this.updateFromDash(view, lastState, this.editorProps);
-    }
-
-
-    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
-        if (this.view) {
-            const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
-            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
-                addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-            return this.view.state.selection.$from.nodeAfter?.text || "";
-        }
-        return "";
-    }
-
-    @action
-    public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
-        if (!view) {
-            console.log("no editor?  why?");
-            return;
-        }
-        this.view = view;
-        const state = view.state;
-        props && (this.editorProps = props);
-
-        // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
-
-        // update active marks
-        const activeMarks = this.getActiveMarksOnSelection();
-        this.setActiveMarkButtons(activeMarks);
-
-        // update active font family and size
-        const active = this.getActiveFontStylesOnSelection();
-        const activeFamilies = active && active.get("families");
-        const activeSizes = active && active.get("sizes");
-
-        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
-
-        // update link in current selection
-        const targetTitle = await this.getTextLinkTargetTitle();
-        this.setCurrentLink(targetTitle);
-    }
-
-    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
-        if (mark) {
-            const node = (state.selection as NodeSelection).node;
-            if (node?.type === schema.nodes.ordered_list) {
-                let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
-                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
-                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
-            } else {
-                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
-                    const { from, $from, to, empty } = tx.selection;
-                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
-                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
-                    } else dispatch(tx);
-                });
-            }
-        }
-    }
-
-    // finds font sizes and families in selection
-    getActiveFontStylesOnSelection() {
-        if (!this.view) return;
-
-        const activeFamilies: string[] = [];
-        const activeSizes: string[] = [];
-        const state = this.view.state;
-        const pos = this.view.state.selection.$from;
-        const ref_node = this.reference_node(pos);
-        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-            ref_node.marks.forEach(m => {
-                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
-                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
-            });
-        }
-
-        const styles = new Map<String, String[]>();
-        styles.set("families", activeFamilies);
-        styles.set("sizes", activeSizes);
-        return styles;
-    }
-
-    getMarksInSelection(state: EditorState<any>) {
-        const found = new Set<Mark>();
-        const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
-        return found;
-    }
-
-    //finds all active marks on selection in given group
-    getActiveMarksOnSelection() {
-        if (!this.view) return;
-
-        const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
-        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
-        //current selection
-        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
-        const state = this.view.state;
-        let activeMarks: MarkType[] = [];
-        if (!empty) {
-            activeMarks = markGroup.filter(mark => {
-                const has = false;
-                for (let i = 0; !has && i < ranges.length; i++) {
-                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
-                }
-                return false;
-            });
-        }
-        else {
-            const pos = this.view.state.selection.$from;
-            const ref_node: ProsNode | null = this.reference_node(pos);
-            if (ref_node !== null && ref_node !== this.view.state.doc) {
-                if (ref_node.isText) {
-                }
-                else {
-                    return [];
-                }
-                activeMarks = markGroup.filter(mark_type => {
-                    if (mark_type === state.schema.marks.pFontSize) {
-                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
-                    }
-                    const mark = state.schema.mark(mark_type);
-                    return ref_node.marks.includes(mark);
-                });
-            }
-        }
-        return activeMarks;
-    }
-
-    destroy() {
-        this.fadeOut(true);
-    }
-
-    @action
-    setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
-        if (!activeMarks) return;
-
-        this.boldActive = false;
-        this.italicsActive = false;
-        this.underlineActive = false;
-        this.strikethroughActive = false;
-        this.subscriptActive = false;
-        this.superscriptActive = false;
-
-        activeMarks.forEach(mark => {
-            switch (mark.name) {
-                case "strong": this.boldActive = true; break;
-                case "em": this.italicsActive = true; break;
-                case "underline": this.underlineActive = true; break;
-                case "strikethrough": this.strikethroughActive = true; break;
-                case "subscript": this.subscriptActive = true; break;
-                case "superscript": this.superscriptActive = true; break;
-            }
-        });
-    }
-
-    createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
-        const self = this;
-        function onClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && command && command(self.view.state, self.view.dispatch, self.view);
-            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
-            self.setActiveMarkButtons(self.getActiveMarksOnSelection());
-        }
-
-        return (
-            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
-                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
-            </button>
-        );
-    }
-
-    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
-        const items = options.map(({ title, label, hidden, style }) => {
-            if (hidden) {
-                return label === activeOption ?
-                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
-            }
-            return label === activeOption ?
-                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
-        });
-
-        const self = this;
-        function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
-            e.stopPropagation();
-            e.preventDefault();
-            options.forEach(({ label, mark, command }) => {
-                if (e.target.value === label) {
-                    self.view && mark && command(mark, self.view);
-                }
-            });
-        }
-        return <select onChange={onChange} key={key}>{items}</select>;
-    }
-
-    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
-        const items = options.map(({ title, label, hidden, style }) => {
-            if (hidden) {
-                return label === activeOption ?
-                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
-            }
-            return label === activeOption ?
-                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
-        });
-
-        const self = this;
-        function onChange(val: string) {
-            options.forEach(({ label, node, command }) => {
-                if (val === label) {
-                    self.view && node && command(node);
-                }
-            });
-        }
-        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
-    }
-
-    changeFontSize = (mark: Mark, view: EditorView) => {
-        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
-    }
-
-    changeFontFamily = (mark: Mark, view: EditorView) => {
-        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
-    }
-
-    // TODO: remove doesn't work
-    //remove all node type and apply the passed-in one to the selected text
-    changeListType = (nodeType: NodeType | undefined) => {
-        if (!this.view) return;
-
-        if (nodeType === schema.nodes.bullet_list) {
-            wrapInList(nodeType)(this.view.state, this.view.dispatch);
-        } else {
-            const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
-            if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                this.view!.dispatch(tx2);
-            })) {
-                const tx2 = this.view.state.tr;
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                this.view.dispatch(tx3);
-            }
-        }
-    }
-
-    insertSummarizer(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        const mark = state.schema.marks.summarize.create();
-        const tr = state.tr;
-        tr.addMark(state.selection.from, state.selection.to, mark);
-        const content = tr.selection.content();
-        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
-        return true;
-    }
-
-    @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
-
-    // todo: add brushes to brushMap to save with a style name
-    onBrushNameKeyPress = (e: React.KeyboardEvent) => {
-        if (e.key === "Enter") {
-            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
-            this._brushNameRef.current!.style.background = "lightGray";
-        }
-    }
-    _brushNameRef = React.createRef<HTMLInputElement>();
-
-    createBrushButton() {
-        const self = this;
-        function onBrushClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && self.fillBrush(self.view.state, self.view.dispatch);
-        }
-
-        let label = "Stored marks: ";
-        if (this.brushMarks && this.brushMarks.size > 0) {
-            this.brushMarks.forEach((mark: Mark) => {
-                const markType = mark.type;
-                label += markType.name;
-                label += ", ";
-            });
-            label = label.substring(0, label.length - 2);
-        } else {
-            label = "No marks are currently stored";
-        }
-
-        const button =
-            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
-                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>{label}</p>
-                <button onPointerDown={this.clearBrush}>Clear brush</button>
-                <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
-        );
-    }
-
-    @action
-    clearBrush() {
-        RichTextMenu.Instance.brushIsEmpty = true;
-        RichTextMenu.Instance.brushMarks = new Set();
-    }
-
-    @action
-    fillBrush(state: EditorState<any>, dispatch: any) {
-        if (!this.view) return;
-
-        if (this.brushIsEmpty) {
-            const selected_marks = this.getMarksInSelection(this.view.state);
-            if (selected_marks.size >= 0) {
-                this.brushMarks = selected_marks;
-                this.brushIsEmpty = !this.brushIsEmpty;
-            }
-        }
-        else {
-            const { from, to, $from } = this.view.state.selection;
-            if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
-                if (this.brushMarks && to - from > 0) {
-                    this.view.dispatch(this.view.state.tr.removeMark(from, to));
-                    Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
-                        this.setMark(mark, this.view!.state, this.view!.dispatch);
-                    });
-                }
-            }
-            else {
-                this.brushIsEmpty = !this.brushIsEmpty;
-            }
-        }
-    }
-
-    @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
-    @action setActiveColor(color: string) { this.activeFontColor = color; }
-
-    createColorButton() {
-        const self = this;
-        function onColorClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
-        }
-        function changeColor(e: React.PointerEvent, color: string) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.setActiveColor(color);
-            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
-        }
-
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
-                <FontAwesomeIcon icon="palette" size="lg" />
-                <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown" >
-                <p>Change font color:</p>
-                <div className="color-wrapper">
-                    {this.fontColors.map(color => {
-                        if (color) {
-                            return this.activeFontColor === color ?
-                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
-                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
-                        }
-                    })}
-                </div>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
-        );
-    }
-
-    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
-        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
-        if (state.selection.empty) {
-            dispatch(state.tr.addStoredMark(colorMark));
-            return false;
-        }
-        this.setMark(colorMark, state, dispatch);
-    }
-
-    @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
-    @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
-
-    createHighlighterButton() {
-        const self = this;
-        function onHighlightClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
-        }
-        function changeHighlight(e: React.PointerEvent, color: string) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.setActiveHighlight(color);
-            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
-        }
-
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
-                <FontAwesomeIcon icon="highlighter" size="lg" />
-                <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>Change highlight color:</p>
-                <div className="color-wrapper">
-                    {this.highlightColors.map(color => {
-                        if (color) {
-                            return this.activeHighlightColor === color ?
-                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
-                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
-                        }
-                    })}
-                </div>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
-        );
-    }
-
-    insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
-    }
-
-    @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
-    @action setCurrentLink(link: string) { this.currentLink = link; }
-
-    createLinkButton() {
-        const self = this;
-
-        function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
-            self.setCurrentLink(e.target.value);
-        }
-
-        const link = this.currentLink ? this.currentLink : "";
-
-        const button = <FontAwesomeIcon icon="link" size="lg" />;
-
-        const dropdownContent =
-            <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>
-                <div className="divider"></div>
-                <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
-        );
-    }
-
-    async getTextLinkTargetTitle() {
-        if (!this.view) return;
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type.name === "link");
-        if (link) {
-            const href = link.attrs.href;
-            if (href) {
-                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    if (linkclicked) {
-                        const linkDoc = await DocServer.GetRefField(linkclicked);
-                        if (linkDoc instanceof Doc) {
-                            const anchor1 = await Cast(linkDoc.anchor1, Doc);
-                            const anchor2 = await Cast(linkDoc.anchor2, Doc);
-                            const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
-                            if (currentDoc && anchor1 && anchor2) {
-                                if (Doc.AreProtosEqual(currentDoc, anchor1)) {
-                                    return StrCast(anchor2.title);
-                                }
-                                if (Doc.AreProtosEqual(currentDoc, anchor2)) {
-                                    return StrCast(anchor1.title);
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    return href;
-                }
-            } else {
-                return link.attrs.title;
-            }
-        }
-    }
-
-    // TODO: should check for valid URL
-    makeLinkToURL = (target: String, lcoation: string) => {
-        if (!this.view) return;
-
-        let node = this.view.state.selection.$from.nodeAfter;
-        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
-        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
-        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-        node = this.view.state.selection.$from.nodeAfter;
-        link = node && node.marks.find(m => m.type.name === "link");
-    }
-
-    deleteLink = () => {
-        if (!this.view) return;
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
-        const href = link!.attrs.href;
-        if (href) {
-            if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                if (linkclicked) {
-                    DocServer.GetRefField(linkclicked).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
-                            LinkManager.Instance.deleteLink(linkDoc);
-                            this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
-                        }
-                    });
-                }
-            } else {
-                if (node) {
-                    const { tr, schema, selection } = this.view.state;
-                    const extension = this.linkExtend(selection.$anchor, href);
-                    this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
-                }
-            }
-        }
-    }
-
-    linkExtend($start: ResolvedPos, href: string) {
-        const mark = this.view!.state.schema.marks.link;
-
-        let startIndex = $start.index();
-        let endIndex = $start.indexAfter();
-
-        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
-        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
-
-        let startPos = $start.start();
-        let endPos = startPos;
-        for (let i = 0; i < endIndex; i++) {
-            const size = $start.parent.child(i).nodeSize;
-            if (i < startIndex) startPos += size;
-            endPos += size;
-        }
-        return { from: startPos, to: endPos };
-    }
-
-    reference_node(pos: ResolvedPos<any>): ProsNode | null {
-        if (!this.view) return null;
-
-        let ref_node: ProsNode = this.view.state.doc;
-        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
-            ref_node = pos.nodeBefore;
-        }
-        else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
-            ref_node = pos.nodeAfter;
-        }
-        else if (pos.pos > 0) {
-            let skip = false;
-            for (let i: number = pos.pos - 1; i > 0; i--) {
-                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
-                    if (node.isLeaf && !skip) {
-                        ref_node = node;
-                        skip = true;
-                    }
-
-                });
-            }
-        }
-        if (!ref_node.isLeaf && ref_node.childCount > 0) {
-            ref_node = ref_node.child(0);
-        }
-        return ref_node;
-    }
-
-    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
-    @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
-
-    @action
-    toggleMenuPin = (e: React.MouseEvent) => {
-        this.Pinned = !this.Pinned;
-        if (!this.Pinned) {
-            this.fadeOut(true);
-        }
-    }
-
-    @action
-    protected toggleCollapse = (e: React.MouseEvent) => {
-        this.collapsed = !this.collapsed;
-        setTimeout(() => {
-            const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
-            RichTextMenu.Instance.jumpTo(x, this._top);
-        }, 0);
-    }
-
-    render() {
-
-        const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
-            this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-            this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-            this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-            this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-            this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-            this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-            this.createColorButton(),
-            this.createHighlighterButton(),
-            this.createLinkButton(),
-            this.createBrushButton(),
-            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
-        ]}</div>;
-
-        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
-            <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
-                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
-                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
-            </div>
-            <div key="button">
-                <div key="collapser">
-                    <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
-                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
-                    </button>
-                </div>
-                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
-                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-                </button>
-                {this.getDragger()}
-            </div>
-        </div>;
-
-        return (
-            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
-                {this.getElementWithRows([row1, row2], 2, false)}
-            </div>
-        );
-    }
-}
-
-interface ButtonDropdownProps {
-    view?: EditorView;
-    button: JSX.Element;
-    dropdownContent: JSX.Element;
-    openDropdownOnButton?: boolean;
-}
-
-@observer
-class ButtonDropdown extends React.Component<ButtonDropdownProps> {
-
-    @observable private showDropdown: boolean = false;
-    private ref: HTMLDivElement | null = null;
-
-    componentDidMount() {
-        document.addEventListener("pointerdown", this.onBlur);
-    }
-
-    componentWillUnmount() {
-        document.removeEventListener("pointerdown", this.onBlur);
-    }
-
-    @action
-    setShowDropdown(show: boolean) {
-        this.showDropdown = show;
-    }
-    @action
-    toggleDropdown() {
-        this.showDropdown = !this.showDropdown;
-    }
-
-    onDropdownClick = (e: React.PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        this.props.view && this.props.view.focus();
-        this.toggleDropdown();
-    }
-
-    onBlur = (e: PointerEvent) => {
-        setTimeout(() => {
-            if (this.ref !== null && !this.ref.contains(e.target as Node)) {
-                this.setShowDropdown(false);
-            }
-        }, 0);
-    }
-
-    render() {
-        return (
-            <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
-                {this.props.openDropdownOnButton ?
-                    <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
-                        {this.props.button}
-                        <FontAwesomeIcon icon="caret-down" size="sm" />
-                    </button> :
-                    <>
-                        {this.props.button}
-                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
-                            <FontAwesomeIcon icon="caret-down" size="sm" />
-                        </button>
-                    </>}
-
-                {this.showDropdown ? this.props.dropdownContent : (null)}
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 63a3815ea..7ce6bbf85 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -7,9 +7,9 @@ import { Cast, NumCast } from "../../new_fields/Types";
 import { returnFalse, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { Docs, DocUtils } from "../documents/Documents";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { wrappingInputRule } from "./prosemirrorPatches";
-import RichTextMenu from "./RichTextMenu";
+import RichTextMenu from "../views/nodes/formattedText/RichTextMenu";
 import { schema } from "./schema_rts";
 
 export class RichTextRules {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
deleted file mode 100644
index 3e45d5de5..000000000
--- a/src/client/util/RichTextSchema.tsx
+++ /dev/null
@@ -1,718 +0,0 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { List } from "../../new_fields/List";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { listSpec } from "../../new_fields/Schema";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { Docs } from "../documents/Documents";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { DocumentManager } from "./DocumentManager";
-import { Transform } from "./Transform";
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-export class OrderedListView {
-    update(node: any) {
-        return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
-    }
-}
-
-export class ImageResizeView {
-    _handle: HTMLElement;
-    _img: HTMLElement;
-    _outer: HTMLElement;
-    constructor(node: any, view: any, getPos: any, addDocTab: any) {
-        //moved
-        this._handle = document.createElement("span");
-        this._img = document.createElement("img");
-        this._outer = document.createElement("span");
-        this._outer.style.position = "relative";
-        this._outer.style.width = node.attrs.width;
-        this._outer.style.height = node.attrs.height;
-        this._outer.style.display = "inline-block";
-        this._outer.style.overflow = "hidden";
-        (this._outer.style as any).float = node.attrs.float;
-        //moved
-        this._img.setAttribute("src", node.attrs.src);
-        this._img.style.width = "100%";
-        this._handle.style.position = "absolute";
-        this._handle.style.width = "20px";
-        this._handle.style.height = "20px";
-        this._handle.style.backgroundColor = "blue";
-        this._handle.style.borderRadius = "15px";
-        this._handle.style.display = "none";
-        this._handle.style.bottom = "-10px";
-        this._handle.style.right = "-10px";
-        const self = this;
-        //moved
-        this._img.onclick = function (e: any) {
-            e.stopPropagation();
-            e.preventDefault();
-            if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
-                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
-            }
-        };
-        //moved
-        this._img.onpointerdown = function (e: any) {
-            if (e.ctrlKey) {
-                e.preventDefault();
-                e.stopPropagation();
-                DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
-                    (linkDoc instanceof Doc) &&
-                    DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
-                        document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false));
-            }
-        };
-        //moved
-        this._handle.onpointerdown = function (e: any) {
-            e.preventDefault();
-            e.stopPropagation();
-            const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
-            const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
-            const startX = e.pageX;
-            const startWidth = parseFloat(node.attrs.width);
-            const onpointermove = (e: any) => {
-                const currentX = e.pageX;
-                const diffInPx = currentX - startX;
-                self._outer.style.width = `${startWidth + diffInPx}`;
-                self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
-            };
-
-            const onpointerup = () => {
-                document.removeEventListener("pointermove", onpointermove);
-                document.removeEventListener("pointerup", onpointerup);
-                const pos = view.state.selection.from;
-                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
-                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
-            };
-
-            document.addEventListener("pointermove", onpointermove);
-            document.addEventListener("pointerup", onpointerup);
-        };
-        //Moved
-        this._outer.appendChild(this._img);
-        this._outer.appendChild(this._handle);
-        (this as any).dom = this._outer;
-    }
-
-    selectNode() {
-        this._img.classList.add("ProseMirror-selectednode");
-
-        this._handle.style.display = "";
-    }
-
-    deselectNode() {
-        this._img.classList.remove("ProseMirror-selectednode");
-
-        this._handle.style.display = "none";
-    }
-}
-
-export class DashDocCommentView {
-    _collapsed: HTMLElement;
-    _view: any;
-    constructor(node: any, view: any, getPos: any) {
-        //moved
-        this._collapsed = document.createElement("span");
-        this._collapsed.className = "formattedTextBox-inlineComment";
-        this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
-        this._view = view;
-        //moved
-        const targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
-            for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
-                const m = view.state.doc.nodeAt(i);
-                if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
-                    return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
-                }
-            }
-            const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
-            view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
-            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
-            return undefined;
-        };
-        //moved
-        this._collapsed.onpointerdown = (e: any) => {
-            e.stopPropagation();
-        };
-        //moved
-        this._collapsed.onpointerup = (e: any) => {
-            const target = targetNode();
-            if (target) {
-                const expand = target.hidden;
-                const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
-                view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
-                setTimeout(() => {
-                    expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
-                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
-                }, 0);
-            }
-            e.stopPropagation();
-        };
-        //moved
-        this._collapsed.onpointerenter = (e: any) => {
-            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
-            e.preventDefault();
-            e.stopPropagation();
-        };
-        //moved
-        this._collapsed.onpointerleave = (e: any) => {
-            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
-            e.preventDefault();
-            e.stopPropagation();
-        };
-
-        (this as any).dom = this._collapsed;
-    }
-    //moved
-    selectNode() { }
-}
-
-export class DashDocView {
-    _dashSpan: HTMLDivElement;
-    _outer: HTMLElement;
-    _dashDoc: Doc | undefined;
-    _reactionDisposer: IReactionDisposer | undefined;
-    _renderDisposer: IReactionDisposer | undefined;
-    _textBox: FormattedTextBox;
-
-    getDocTransform = () => {
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
-        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
-    }
-    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
-
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this._textBox = tbox;
-        this._dashSpan = document.createElement("div");
-        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().activeWorkspace, Doc, null).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";
-        // this._outer.style.overflow = "hidden";  // bcz: not sure if this is needed.  if it's used, then the doc doesn't highlight when you hover over a docComment
-        (this._outer.style as any).float = node.attrs.float;
-
-        this._dashSpan.style.width = node.attrs.width;
-        this._dashSpan.style.height = node.attrs.height;
-        this._dashSpan.style.position = "absolute";
-        this._dashSpan.style.display = "inline-block";
-        this._dashSpan.onpointerleave = () => {
-            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
-            if (ele) {
-                (ele as HTMLDivElement).style.backgroundColor = "";
-            }
-        };
-        this._dashSpan.onpointerenter = () => {
-            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
-            if (ele) {
-                (ele as HTMLDivElement).style.backgroundColor = "orange";
-            }
-        };
-        const removeDoc = () => {
-            const pos = getPos();
-            const ns = new NodeSelection(view.state.doc.resolve(pos));
-            view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
-            return true;
-        };
-        const alias = node.attrs.alias;
-
-        const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
-        DocServer.GetRefField(docid + alias).then(async dashDoc => {
-            if (!(dashDoc instanceof Doc)) {
-                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
-                    if (dashDocBase instanceof Doc) {
-                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
-                        aliasedDoc.layoutKey = "layout";
-                        node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
-                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
-                    }
-                });
-            } else {
-                self.doRender(dashDoc, removeDoc, node, view, getPos);
-            }
-        });
-        const self = this;
-        this._dashSpan.onkeydown = function (e: any) {
-            e.stopPropagation();
-            if (e.key === "Tab" || e.key === "Enter") {
-                e.preventDefault();
-            }
-        };
-        this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
-        this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
-        this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._outer.appendChild(this._dashSpan);
-        (this as any).dom = this._outer;
-    }
-
-    doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
-        this._dashDoc = dashDoc;
-        const self = this;
-        const dashLayoutDoc = Doc.Layout(dashDoc);
-        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
-
-        if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
-        else {
-            this._reactionDisposer?.();
-            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().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
-            }, { fireImmediately: true });
-            const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
-                ReactDOM.unmountComponentAtNode(this._dashSpan);
-
-                ReactDOM.render(<DocumentView
-                    Document={finalLayout}
-                    DataDoc={resolvedDataDoc}
-                    LibraryPath={this._textBox.props.LibraryPath}
-                    fitToBox={BoolCast(dashDoc._fitToBox)}
-                    addDocument={returnFalse}
-                    rootSelected={this._textBox.props.isSelected}
-                    removeDocument={removeDoc}
-                    ScreenToLocalTransform={this.getDocTransform}
-                    addDocTab={this._textBox.props.addDocTab}
-                    pinToPres={returnFalse}
-                    renderDepth={self._textBox.props.renderDepth + 1}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
-                    PanelWidth={finalLayout[WidthSym]}
-                    PanelHeight={finalLayout[HeightSym]}
-                    focus={this.outerFocus}
-                    backgroundColor={returnEmptyString}
-                    parentActive={returnFalse}
-                    whenActiveChanged={returnFalse}
-                    bringToFront={emptyFunction}
-                    dontRegisterView={false}
-                    ContainingCollectionView={this._textBox.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
-                    ContentScaling={this.contentScaling}
-                />, this._dashSpan);
-                if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
-                    try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
-                        view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
-                    } catch (e) {
-                        console.log(e);
-                    }
-                }
-            };
-            this._renderDisposer?.();
-            this._renderDisposer = reaction(() => {
-                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
-                // }
-                const layoutKey = StrCast(finalLayout.layoutKey);
-                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
-                if (finalLayout !== dashDoc && finalKey) {
-                    const finalLayoutField = finalLayout[finalKey];
-                    if (finalLayoutField instanceof ObjectField) {
-                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
-                    }
-                }
-                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
-            },
-                (res) => doReactRender(res.finalLayout, res.resolvedDataDoc),
-                { fireImmediately: true });
-        }
-    }
-    destroy() {
-        ReactDOM.unmountComponentAtNode(this._dashSpan);
-        this._reactionDisposer?.();
-    }
-}
-
-export class DashFieldView {
-    _fieldWrapper: HTMLDivElement; // container for label and value
-    _labelSpan: HTMLSpanElement; // field label
-    _fieldSpan: HTMLSpanElement;  // field value
-    _fieldCheck: HTMLInputElement;
-    _enumerables: HTMLDivElement;  // field value
-    _reactionDisposer: IReactionDisposer | undefined;
-    _textBoxDoc: Doc;
-    @observable _dashDoc: Doc | undefined;
-    _fieldKey: string;
-    _options: Doc[] = [];
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this._fieldKey = node.attrs.fieldKey;
-        this._textBoxDoc = tbox.props.Document;
-        this._fieldWrapper = document.createElement("p");
-        this._fieldWrapper.style.width = node.attrs.width;
-        this._fieldWrapper.style.height = node.attrs.height;
-        this._fieldWrapper.style.fontWeight = "bold";
-        this._fieldWrapper.style.position = "relative";
-        this._fieldWrapper.style.display = "inline-block";
-
-        const self = this;
-
-        this._enumerables = document.createElement("div");
-        this._enumerables.style.width = "10px";
-        this._enumerables.style.height = "10px";
-        this._enumerables.style.position = "relative";
-        this._enumerables.style.display = "none";
-
-        //Moved
-        this._enumerables.onpointerdown = async (e) => {
-            e.stopPropagation();
-            const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
-            collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
-        };
-        //Moved
-        const updateText = (forceMatch: boolean) => {
-            self._enumerables.style.display = "none";
-            const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
-
-            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
-            // holds the different enumerated values for the field in the titles of its collected documents.
-            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
-            DocServer.GetRefField(self._fieldKey).then(options => {
-                let modText = "";
-                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
-                if (modText) {
-                    self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText;
-                    Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []);
-                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
-                else if (self._fieldSpan.innerText.startsWith(":=")) {
-                    self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
-                } else if (self._fieldSpan.innerText.startsWith("=:=")) {
-                    Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3));
-                } else {
-                    self._dashDoc![self._fieldKey] = newText;
-                }
-            });
-        };
-
-        //Moved
-        this._fieldCheck = document.createElement("input");
-        this._fieldCheck.id = Utils.GenerateGuid();
-        this._fieldCheck.type = "checkbox";
-        this._fieldCheck.style.position = "relative";
-        this._fieldCheck.style.display = "none";
-        this._fieldCheck.style.minWidth = "12px";
-        this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
-        this._fieldCheck.onchange = function (e: any) {
-            self._dashDoc![self._fieldKey] = e.target.checked;
-        };
-
-        this._fieldSpan = document.createElement("span");
-        this._fieldSpan.id = Utils.GenerateGuid();
-        this._fieldSpan.contentEditable = "true";
-        this._fieldSpan.style.position = "relative";
-        this._fieldSpan.style.display = "none";
-        this._fieldSpan.style.minWidth = "12px";
-        this._fieldSpan.style.fontSize = "large";
-        this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
-        this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
-        this._fieldSpan.onblur = function (e: any) { updateText(false); };
-
-        // MOVED 
-        const setDashDoc = (doc: Doc) => {
-            self._dashDoc = doc;
-            if (self._options?.length && !self._dashDoc[self._fieldKey]) {
-                self._dashDoc[self._fieldKey] = StrCast(self._options[0].title);
-            }
-            this._labelSpan.innerHTML = `${self._fieldKey}: `;
-            const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
-            this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
-            this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none";
-        };
-
-        //Moved
-        this._fieldSpan.onkeydown = function (e: any) {
-            e.stopPropagation();
-            if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
-                if (window.getSelection) {
-                    const range = document.createRange();
-                    range.selectNodeContents(self._fieldSpan);
-                    window.getSelection()!.removeAllRanges();
-                    window.getSelection()!.addRange(range);
-                }
-                e.preventDefault();
-            }
-            if (e.key === "Enter") {
-                e.preventDefault();
-                e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
-                updateText(true);
-            }
-        };
-
-        this._labelSpan = document.createElement("span");
-        this._labelSpan.style.position = "relative";
-        this._labelSpan.style.fontSize = "small";
-        this._labelSpan.title = "click to see related tags";
-        this._labelSpan.style.fontSize = "x-small";
-        this._labelSpan.onpointerdown = function (e: any) {
-            e.stopPropagation();
-            let container = tbox.props.ContainingCollectionView;
-            while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
-                container = container.props.ContainingCollectionView;
-            }
-            if (container) {
-                const alias = Doc.MakeAlias(container.props.Document);
-                alias.viewType = CollectionViewType.Time;
-                let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
-                if (!list) {
-                    alias.schemaColumns = list = new List<SchemaHeaderField>();
-                }
-                list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb"));
-                list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
-                alias._pivotField = self._fieldKey;
-                tbox.props.addDocTab(alias, "onRight");
-            }
-        };
-        this._labelSpan.innerHTML = `${self._fieldKey}: `;
-        //MOVED
-        if (node.attrs.docid) {
-            DocServer.GetRefField(node.attrs.docid).
-                then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
-        } else {
-            setDashDoc(tbox.props.DataDoc || tbox.dataDoc);
-        }
-
-        //Moved
-        this._reactionDisposer?.();
-        this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes
-            const dashVal = this._dashDoc?.[self._fieldKey];
-            return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal;
-        }, fval => {
-            const boolVal = Cast(fval, "boolean", null);
-            if (boolVal === true || boolVal === false) {
-                this._fieldCheck.checked = boolVal;
-            } else {
-                this._fieldSpan.innerHTML = Field.toString(fval as Field) || "";
-            }
-            this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none";
-            this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none";
-        }, { fireImmediately: true });
-
-        //MOVED IN ORDER
-        this._fieldWrapper.appendChild(this._labelSpan);
-        this._fieldWrapper.appendChild(this._fieldCheck);
-        this._fieldWrapper.appendChild(this._fieldSpan);
-        this._fieldWrapper.appendChild(this._enumerables);
-        (this as any).dom = this._fieldWrapper;
-        //updateText(false);
-    }
-    //MOVED
-    destroy() {
-        this._reactionDisposer?.();
-    }
-    //moved
-    selectNode() { }
-}
-
-export class FootnoteView {
-    innerView: any;
-    outerView: any;
-    node: any;
-    dom: any;
-    getPos: any;
-
-    constructor(node: any, view: any, getPos: any) {
-        // We'll need these later
-        this.node = node;
-        this.outerView = view;
-        this.getPos = getPos;
-
-        // The node's representation in the editor (empty, for now)
-        this.dom = document.createElement("footnote");
-        this.dom.addEventListener("pointerup", this.toggle, true);
-        // These are used when the footnote is selected
-        this.innerView = null;
-    }
-    selectNode() {
-        const attrs = { ...this.node.attrs };
-        attrs.visibility = true;
-        this.dom.classList.add("ProseMirror-selectednode");
-        if (!this.innerView) this.open();
-    }
-
-    deselectNode() {
-        const attrs = { ...this.node.attrs };
-        attrs.visibility = false;
-        this.dom.classList.remove("ProseMirror-selectednode");
-        if (this.innerView) this.close();
-    }
-    open() {
-        // Append a tooltip to the outer node
-        const tooltip = this.dom.appendChild(document.createElement("div"));
-        tooltip.className = "footnote-tooltip";
-        // And put a sub-ProseMirror into that
-        this.innerView = new EditorView(tooltip, {
-            // You can use any node as an editor document
-            state: EditorState.create({
-                doc: this.node,
-                plugins: [keymap(baseKeymap),
-                keymap({
-                    "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
-                    "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
-                    "Mod-b": toggleMark(schema.marks.strong)
-                }),
-                    // new Plugin({
-                    //     view(newView) {
-                    //         // TODO -- make this work with RichTextMenu
-                    //         // return FormattedTextBox.getToolTip(newView);
-                    //     }
-                    // })
-                ],
-
-            }),
-            // This is the magic part
-            dispatchTransaction: this.dispatchInner.bind(this),
-            handleDOMEvents: {
-                pointerdown: ((view: any, e: PointerEvent) => {
-                    // Kludge to prevent issues due to the fact that the whole
-                    // footnote is node-selected (and thus DOM-selected) when
-                    // the parent editor is focused.
-                    e.stopPropagation();
-                    document.addEventListener("pointerup", this.ignore, true);
-                    if (this.outerView.hasFocus()) this.innerView.focus();
-                }) as any
-            }
-
-        });
-        setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
-    }
-
-    ignore = (e: PointerEvent) => {
-        e.stopPropagation();
-        document.removeEventListener("pointerup", this.ignore, true);
-    }
-
-    toggle = () => {
-        if (this.innerView) this.close();
-        else {
-            this.open();
-        }
-    }
-    close() {
-        this.innerView && this.innerView.destroy();
-        this.innerView = null;
-        this.dom.textContent = "";
-    }
-
-    dispatchInner(tr: any) {
-        const { state, transactions } = this.innerView.state.applyTransaction(tr);
-        this.innerView.updateState(state);
-
-        if (!tr.getMeta("fromOutside")) {
-            const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
-            for (const transaction of transactions) {
-                const steps = transaction.steps;
-                for (const step of steps) {
-                    outerTr.step(step.map(offsetMap));
-                }
-            }
-            if (outerTr.docChanged) this.outerView.dispatch(outerTr);
-        }
-    }
-    update(node: any) {
-        if (!node.sameMarkup(this.node)) return false;
-        this.node = node;
-        if (this.innerView) {
-            const state = this.innerView.state;
-            const start = node.content.findDiffStart(state.doc.content);
-            if (start !== null) {
-                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
-                const overlap = start - Math.min(endA, endB);
-                if (overlap > 0) { endA += overlap; endB += overlap; }
-                this.innerView.dispatch(
-                    state.tr
-                        .replace(start, endB, node.slice(start, endA))
-                        .setMeta("fromOutside", true));
-            }
-        }
-        return true;
-    }
-
-    destroy() {
-        if (this.innerView) this.close();
-    }
-
-    stopEvent(event: any) {
-        return this.innerView && this.innerView.dom.contains(event.target);
-    }
-
-    ignoreMutation() { return true; }
-}
-
-export class SummaryView {
-    _collapsed: HTMLElement;
-    _view: any;
-    constructor(node: any, view: any, getPos: any) {
-        this._collapsed = document.createElement("span");
-        this._collapsed.className = this.className(node.attrs.visibility);
-        this._view = view;
-        const js = node.toJSON;
-        node.toJSON = function () {
-            return js.apply(this, arguments);
-        };
-
-        this._collapsed.onpointerdown = (e: any) => {
-            const visible = !node.attrs.visibility;
-            const attrs = { ...node.attrs, visibility: visible };
-            let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
-            if (!visible) { // update summarized text and save in attrs
-                textSelection = this.updateSummarizedText(getPos() + 1);
-                attrs.text = textSelection.content();
-                attrs.textslice = attrs.text.toJSON();
-            }
-            view.dispatch(view.state.tr.
-                setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
-                replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
-                setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
-            e.preventDefault();
-            e.stopPropagation();
-            this._collapsed.className = this.className(visible);
-        };
-        (this as any).dom = this._collapsed;
-    }
-    selectNode() { }
-
-    deselectNode() { }
-
-    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
-    updateSummarizedText(start?: any) {
-        const mtype = this._view.state.schema.marks.summarize;
-        const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
-        let endPos = start;
-
-        const visited = new Set();
-        for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
-            let skip = false;
-            this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
-                if (node.isLeaf && !visited.has(node) && !skip) {
-                    if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
-                        visited.add(node);
-                        endPos = i + node.nodeSize - 1;
-                    }
-                    else skip = true;
-                }
-            });
-        }
-        return TextSelection.create(this._view.state.doc, start, endPos);
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/SummaryView.tsx b/src/client/util/SummaryView.tsx
deleted file mode 100644
index 89908d8ee..000000000
--- a/src/client/util/SummaryView.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { TextSelection } from "prosemirror-state";
-import { Fragment, Node, Slice } from "prosemirror-model";
-
-import React = require("react");
-
-interface ISummaryView {
-    node: any;
-    view: any;
-    getPos: any;
-    self: any;
-}
-export class SummaryView extends React.Component<ISummaryView> {
-
-    onPointerDown = (e: any) => {
-        const visible = !this.props.node.attrs.visibility;
-        const attrs = { ...this.props.node.attrs, visibility: visible };
-        let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1);
-        if (!visible) { // update summarized text and save in attrs
-            textSelection = this.updateSummarizedText(this.props.getPos() + 1);
-            attrs.text = textSelection.content();
-            attrs.textslice = attrs.text.toJSON();
-        }
-        this.props.view.dispatch(this.props.view.state.tr.
-            setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
-            replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it
-            setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs
-        e.preventDefault();
-        e.stopPropagation();
-        const _collapsed = document.getElementById('collapse') as HTMLElement;
-        _collapsed.className = this.className(visible);
-    }
-
-    updateSummarizedText(start?: any) {
-        const mtype = this.props.view.state.schema.marks.summarize;
-        const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive;
-        let endPos = start;
-
-        const visited = new Set();
-        for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) {
-            let skip = false;
-            this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
-                if (this.props.node.isLeaf && !visited.has(node) && !skip) {
-                    if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
-                        visited.add(node);
-                        endPos = i + this.props.node.nodeSize - 1;
-                    }
-                    else skip = true;
-                }
-            });
-        }
-        return TextSelection.create(this.props.view.state.doc, start, endPos);
-    }
-
-    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
-    selectNode() { }
-
-    deselectNode() { }
-
-    render() {
-        const _view = this.props.node.view;
-        const js = this.props.node.toJSon;
-
-        this.props.node.toJSON = function () {
-            return js.apply(this, arguments);
-        };
-
-        const spanCollapsedClassName = this.className(this.props.node.attrs.visibility);
-
-        return (
-            <span
-                className={spanCollapsedClassName}
-                id='collapse'
-                onPointerDown={this.onPointerDown}
-            >
-
-            </span>
-        );
-
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
deleted file mode 100644
index e2149e9c1..000000000
--- a/src/client/util/TooltipTextMenu.scss
+++ /dev/null
@@ -1,372 +0,0 @@
-@import "../views/globalCssVariables";
-.ProseMirror-menu-dropdown-wrap {
-    display: inline-block;
-    position: relative;
-}
-  
-.ProseMirror-menu-dropdown {
-    vertical-align: 1px;
-    cursor: pointer;
-    position: relative;
-    padding: 0 15px 0 4px;
-    background: white;
-    border-radius: 2px;
-    text-align: left;
-    font-size: 12px;
-    white-space: nowrap;
-    margin-right: 4px;
-
-    &:after {
-        content: "";
-        border-left: 4px solid transparent;
-        border-right: 4px solid transparent;
-        border-top: 4px solid currentColor;
-        opacity: .6;
-        position: absolute;
-        right: 4px;
-        top: calc(50% - 2px);
-    }
-}
-
-.ProseMirror-menu-submenu-wrap {
-    position: relative;
-    margin-right: -4px;
-}
-
-.ProseMirror-menu-dropdown-menu, 
-.ProseMirror-menu-submenu {
-    font-size: 12px;
-    background: white;
-    border: 1px solid rgb(223, 223, 223);
-    min-width: 40px;
-    z-index: 50000;
-    position: absolute;
-    box-sizing: content-box;
-
-    .ProseMirror-menu-dropdown-item {
-        cursor: pointer;
-        z-index: 100000;
-        text-align: left;
-        padding: 3px;
-
-        &:hover {
-            background-color: $light-color-secondary;
-        }
-    }
-}
-
-
-.ProseMirror-menu-submenu-label:after {
-    content: "";
-    border-top: 4px solid transparent;
-    border-bottom: 4px solid transparent;
-    border-left: 4px solid currentColor;
-    opacity: .6;
-    position: absolute;
-    right: 4px;
-    top: calc(50% - 4px);
-}
-  
-  .ProseMirror-icon {
-    display: inline-block;
-    // line-height: .8;
-    // vertical-align: -2px; /* Compensate for padding */
-    // padding: 2px 8px;
-    cursor: pointer;
-
-    &.ProseMirror-menu-disabled {
-        cursor: default;
-    }
-
-    svg {
-        fill:white;
-        height: 1em;
-    }
-
-    span {
-        vertical-align: text-top;
-      }  
-  }
-
-.wrapper {
-    position: absolute;
-    pointer-events: all;
-    display: flex;
-    align-items: center;
-    transform: translateY(-85px);
-
-    height: 35px;
-    background: #323232;
-    border-radius: 6px;
-    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-}
-
-.tooltipMenu, .basic-tools {
-    z-index: 20000;
-    pointer-events: all;
-    padding: 3px;
-    padding-bottom: 5px;
-    display: flex;
-    align-items: center;
-
-    .ProseMirror-example-setup-style hr {
-      padding: 2px 10px;
-      border: none;
-      margin: 1em 0;
-    }
-    
-    .ProseMirror-example-setup-style hr:after {
-      content: "";
-      display: block;
-      height: 1px;
-      background-color: silver;
-      line-height: 2px;
-    }
-}
-
-.menuicon {
-    width: 25px;
-    height: 25px;
-    cursor: pointer;
-    text-align: center;
-    line-height: 25px;
-    margin: 0 2px;
-    border-radius: 3px;
-
-    &:hover {
-        background-color: black;
-
-        #link-drag {
-            background-color: black;
-        }
-    }
-
-    &> * {
-        margin-top: 50%;
-        margin-left: 50%;
-        transform: translate(-50%, -50%);
-    }
-
-    svg {
-        fill: white;
-        width: 18px;
-        height: 18px;
-    }
-}
-
-.menuicon-active {
-    width: 25px;
-    height: 25px;
-    cursor: pointer;
-    text-align: center;
-    line-height: 25px;
-    margin: 0 2px;
-    border-radius: 3px;
-
-    &:hover {
-        background-color: black;
-    }
-
-    &> * {
-        margin-top: 50%;
-        margin-left: 50%;
-        transform: translate(-50%, -50%);
-    }
-
-    svg {
-        fill: greenyellow;
-        width: 18px;
-        height: 18px;
-    }
-}
-
-#colorPicker {
-    position: relative;
-
-    svg {
-        width: 18px;
-        height: 18px;
-        // margin-top: 11px;
-    }
-
-    .buttonColor {
-        position: absolute;
-        top: 24px;
-        left: 1px;
-        width: 24px;
-        height: 4px;
-        margin-top: 0;
-    }
-}
-
-#link-drag {
-    background-color: #323232;
-}
-
-.underline svg {
-    margin-top: 13px;
-}
-
-  .font-size-indicator {
-      font-size: 12px;
-      padding-right: 0px;
-  }
-  .summarize{
-      color: white;
-      height: 20px;
-      text-align: center;
-  }
-  
-
-.brush{
-    display: inline-block;
-    width: 1em;
-    height: 1em;
-    stroke-width: 0;
-    stroke: currentColor;
-    fill: currentColor;
-    margin-right: 15px;
-}
-
-.brush-active{
-    display: inline-block;
-    width: 1em;
-    height: 1em;
-    stroke-width: 3;
-    fill: greenyellow;
-    margin-right: 15px;
-}
-
-.dragger-wrapper {
-    color: #eee;
-    height: 22px;
-    padding: 0 5px;
-    box-sizing: content-box;
-    cursor: grab;
-
-    .dragger {
-        width: 18px;
-        height: 100%;
-        display: flex;
-        justify-content: space-evenly;
-    }
-
-    .dragger-line {
-        width: 2px;
-        height: 100%;
-        background-color: black;
-    }
-}
-
-.button-dropdown-wrapper {
-    display: flex;
-    align-content: center;
-
-    &:hover {
-        background-color: black;
-    }
-}
-
-.buttonSettings-dropdown {
-
-    &.ProseMirror-menu-dropdown {
-        width: 10px;
-        height: 25px;
-        margin: 0;
-        padding: 0 2px;
-        background-color: #323232;
-        text-align: center;
-
-        &:after {
-            border-top: 4px solid white;
-            right: 2px;
-        }
-
-        &:hover {
-            background-color: black;
-        }
-    }
-
-    &.ProseMirror-menu-dropdown-menu {
-        min-width: 150px;
-        left: -27px;
-        top: 31px;
-        background-color: #323232;
-        border: 1px solid #4d4d4d;
-        color: $light-color-secondary;
-        // border: none;
-        // border: 1px solid $light-color-secondary;
-        border-radius: 0 6px 6px 6px;
-        padding: 3px;
-        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-        .ProseMirror-menu-dropdown-item{
-            cursor: default;
-
-            &:last-child {
-                border-bottom: none;
-            }
-
-            &:hover {
-                background-color: #323232;
-            }
-
-            .button-setting, .button-setting-disabled {
-                padding: 2px;
-                border-radius: 2px;
-            }
-
-            .button-setting:hover {
-                cursor: pointer;
-                background-color: black;
-            }
-
-            .separated-button {
-                border-top: 1px solid $light-color-secondary;
-                padding-top: 6px;
-            }
-
-            input {
-               color: black;
-               border: none;
-               border-radius: 1px;
-               padding: 3px;
-            }
-
-            button {
-               padding: 6px;
-               background-color: #323232;
-               border: 1px solid black;
-               border-radius: 1px;
-
-                &:hover {
-                   background-color: black;
-                }
-            }
-        }
-
-
-    }
-}
-
-.colorPicker-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-    margin-top: 3px;
-    margin-left: -3px;
-    width: calc(100% + 6px);
-}
-
-button.colorPicker {
-    width: 20px;
-    height: 20px;
-    border-radius: 15px !important;
-    margin: 3px;
-    border: none !important;
-
-    &.active {
-        border: 2px solid white !important;
-    }
-}
diff --git a/src/client/util/marks_rts.ts b/src/client/util/marks_rts.ts
deleted file mode 100644
index 75d7109e4..000000000
--- a/src/client/util/marks_rts.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { Doc } from "../../new_fields/Doc";
-
-
-const emDOM: DOMOutputSpecArray = ["em", 0];
-const strongDOM: DOMOutputSpecArray = ["strong", 0];
-const codeDOM: DOMOutputSpecArray = ["code", 0];
-
-// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
-export const marks: { [index: string]: MarkSpec } = {
-    // :: MarkSpec A link. Has `href` and `title` attributes. `title`
-    // defaults to the empty string. Rendered and parsed as an `<a>`
-    // element.
-    link: {
-        attrs: {
-            href: {},
-            targetId: { default: "" },
-            linkId: { default: "" },
-            showPreview: { default: true },
-            location: { default: null },
-            title: { default: null },
-            docref: { default: false } // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
-        },
-        inclusive: false,
-        parseDOM: [{
-            tag: "a[href]", getAttrs(dom: any) {
-                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
-            }
-        }],
-        toDOM(node: any) {
-            return node.attrs.docref && node.attrs.title ?
-                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
-                ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0];
-        }
-    },
-
-
-    // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
-    pFontColor: {
-        attrs: {
-            color: { default: "#000" }
-        },
-        inclusive: true,
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                return { color: dom.getAttribute("color") };
-            }
-        }],
-        toDOM(node: any) {
-            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
-        }
-    },
-
-    marker: {
-        attrs: {
-            highlight: { default: "transparent" }
-        },
-        inclusive: true,
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                return { highlight: dom.getAttribute("backgroundColor") };
-            }
-        }],
-        toDOM(node: any) {
-            return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
-        }
-    },
-
-    // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
-    // Has parse rules that also match `<i>` and `font-style: italic`.
-    em: {
-        parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
-        toDOM() { return emDOM; }
-    },
-
-    // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
-    // also match `<b>` and `font-weight: bold`.
-    strong: {
-        parseDOM: [{ tag: "strong" },
-        { tag: "b" },
-        { style: "font-weight" }],
-        toDOM() { return strongDOM; }
-    },
-
-    strikethrough: {
-        parseDOM: [
-            { tag: 'strike' },
-            { style: 'text-decoration=line-through' },
-            { style: 'text-decoration-line=line-through' }
-        ],
-        toDOM: () => ['span', {
-            style: 'text-decoration-line:line-through'
-        }]
-    },
-
-    subscript: {
-        excludes: 'superscript',
-        parseDOM: [
-            { tag: 'sub' },
-            { style: 'vertical-align=sub' }
-        ],
-        toDOM: () => ['sub']
-    },
-
-    superscript: {
-        excludes: 'subscript',
-        parseDOM: [
-            { tag: 'sup' },
-            { style: 'vertical-align=super' }
-        ],
-        toDOM: () => ['sup']
-    },
-
-    mbulletType: {
-        attrs: {
-            bulletType: { default: "decimal" }
-        },
-        toDOM(node: any) {
-            return ['span', {
-                style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
-            }];
-        }
-    },
-
-    metadata: {
-        toDOM() {
-            return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
-        }
-    },
-    metadataKey: {
-        toDOM() {
-            return ['span', { style: 'font-style:italic; ' }];
-        }
-    },
-    metadataVal: {
-        toDOM() {
-            return ['span'];
-        }
-    },
-
-    summarizeInclusive: {
-        parseDOM: [
-            {
-                tag: "span",
-                getAttrs: (p: any) => {
-                    if (typeof (p) !== "string") {
-                        const style = getComputedStyle(p);
-                        if (style.textDecoration === "underline") return null;
-                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
-                            p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
-                            return null;
-                        }
-                    }
-                    return false;
-                }
-            },
-        ],
-        inclusive: true,
-        toDOM() {
-            return ['span', {
-                style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
-            }];
-        }
-    },
-
-    summarize: {
-        inclusive: false,
-        parseDOM: [
-            {
-                tag: "span",
-                getAttrs: (p: any) => {
-                    if (typeof (p) !== "string") {
-                        const style = getComputedStyle(p);
-                        if (style.textDecoration === "underline") return null;
-                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
-                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
-                            return null;
-                        }
-                    }
-                    return false;
-                }
-            },
-        ],
-        toDOM() {
-            return ['span', {
-                style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
-            }];
-        }
-    },
-
-    underline: {
-        parseDOM: [
-            {
-                tag: "span",
-                getAttrs: (p: any) => {
-                    if (typeof (p) !== "string") {
-                        const style = getComputedStyle(p);
-                        if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
-                            return null;
-                        }
-                    }
-                    return false;
-                }
-            }
-            // { style: "text-decoration=underline" }
-        ],
-        toDOM: () => ['span', {
-            style: 'text-decoration:underline;text-decoration-style:line'
-        }]
-    },
-
-    search_highlight: {
-        attrs: {
-            selected: { default: false }
-        },
-        parseDOM: [{ style: 'background: yellow' }],
-        toDOM(node: any) {
-            return ['span', {
-                style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
-            }];
-        }
-    },
-
-    // the id of the user who entered the text
-    user_mark: {
-        attrs: {
-            userid: { default: "" },
-            modified: { default: "when?" }, // 1 second intervals since 1970
-        },
-        group: "inline",
-        toDOM(node: any) {
-            const uid = node.attrs.userid.replace(".", "").replace("@", "");
-            const min = Math.round(node.attrs.modified / 12);
-            const hr = Math.round(min / 60);
-            const day = Math.round(hr / 60 / 24);
-            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
-            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
-        }
-    },
-    // the id of the user who entered the text
-    user_tag: {
-        attrs: {
-            userid: { default: "" },
-            modified: { default: "when?" }, // 1 second intervals since 1970
-            tag: { default: "" }
-        },
-        group: "inline",
-        inclusive: false,
-        toDOM(node: any) {
-            const uid = node.attrs.userid.replace(".", "").replace("@", "");
-            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
-        }
-    },
-
-
-    // :: MarkSpec Code font mark. Represented as a `<code>` element.
-    code: {
-        parseDOM: [{ tag: "code" }],
-        toDOM() { return codeDOM; }
-    },
-
-    /* FONTS */
-    pFontFamily: {
-        attrs: {
-            family: { default: "Crimson Text" },
-        },
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                const cstyle = getComputedStyle(dom);
-                if (cstyle.font) {
-                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
-                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
-                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
-                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
-                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
-                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
-                }
-            }
-        }],
-        toDOM: (node) => ['span', {
-            style: `font-family: "${node.attrs.family}";`
-        }]
-    },
-
-    /** FONT SIZES */
-    pFontSize: {
-        attrs: {
-            fontSize: { default: 10 }
-        },
-        parseDOM: [{ style: 'font-size: 10px;' }],
-        toDOM: (node) => ['span', {
-            style: `font-size: ${node.attrs.fontSize}px;`
-        }]
-    },
-};
diff --git a/src/client/util/nodes_rts.ts b/src/client/util/nodes_rts.ts
deleted file mode 100644
index e7bcf444a..000000000
--- a/src/client/util/nodes_rts.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import ParagraphNodeSpec from "./ParagraphNodeSpec";
-
-const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
-    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
-
-// :: Object
-// [Specs](#model.NodeSpec) for the nodes defined in this schema.
-export const nodes: { [index: string]: NodeSpec } = {
-    // :: NodeSpec The top level document node.
-    doc: {
-        content: "block+"
-    },
-
-    footnote: {
-        group: "inline",
-        content: "inline*",
-        inline: true,
-        attrs: {
-            visibility: { default: false }
-        },
-        // This makes the view treat the node as a leaf, even though it
-        // technically has content
-        atom: true,
-        toDOM: () => ["footnote", 0],
-        parseDOM: [{ tag: "footnote" }]
-    },
-
-    paragraph: ParagraphNodeSpec,
-
-    // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
-    blockquote: {
-        content: "block+",
-        group: "block",
-        defining: true,
-        parseDOM: [{ tag: "blockquote" }],
-        toDOM() { return blockquoteDOM; }
-    },
-
-    // :: NodeSpec A horizontal rule (`<hr>`).
-    horizontal_rule: {
-        group: "block",
-        parseDOM: [{ tag: "hr" }],
-        toDOM() { return hrDOM; }
-    },
-
-    // :: NodeSpec A heading textblock, with a `level` attribute that
-    // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
-    // `<h6>` elements.
-    heading: {
-        attrs: { level: { default: 1 } },
-        content: "inline*",
-        group: "block",
-        defining: true,
-        parseDOM: [{ tag: "h1", attrs: { level: 1 } },
-        { tag: "h2", attrs: { level: 2 } },
-        { tag: "h3", attrs: { level: 3 } },
-        { tag: "h4", attrs: { level: 4 } },
-        { tag: "h5", attrs: { level: 5 } },
-        { tag: "h6", attrs: { level: 6 } }],
-        toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
-    },
-
-    // :: NodeSpec A code listing. Disallows marks or non-text inline
-    // nodes by default. Represented as a `<pre>` element with a
-    // `<code>` element inside of it.
-    code_block: {
-        content: "text*",
-        marks: "",
-        group: "block",
-        code: true,
-        defining: true,
-        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-        toDOM() { return preDOM; }
-    },
-
-    // :: NodeSpec The text node.
-    text: {
-        group: "inline"
-    },
-
-    dashComment: {
-        attrs: {
-            docid: { default: "" },
-        },
-        inline: true,
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }, "←"];
-        },
-    },
-
-    summary: {
-        inline: true,
-        attrs: {
-            visibility: { default: false },
-            text: { default: undefined },
-            textslice: { default: undefined },
-        },
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }];
-        },
-    },
-
-    // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
-    // `alt`, and `href` attributes. The latter two default to the empty
-    // string.
-    image: {
-        inline: true,
-        attrs: {
-            src: {},
-            agnostic: { default: null },
-            width: { default: 100 },
-            alt: { default: null },
-            title: { default: null },
-            float: { default: "left" },
-            location: { default: "onRight" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "img[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        // TODO if we don't define toDom, dragging the image crashes. Why?
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["img", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashDoc: {
-        inline: true,
-        attrs: {
-            width: { default: 200 },
-            height: { default: 100 },
-            title: { default: null },
-            float: { default: "right" },
-            location: { default: "onRight" },
-            hidden: { default: false },
-            fieldKey: { default: "" },
-            docid: { default: "" },
-            alias: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashField: {
-        inline: true,
-        attrs: {
-            fieldKey: { default: "" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    video: {
-        inline: true,
-        attrs: {
-            src: {},
-            width: { default: "100px" },
-            alt: { default: null },
-            title: { default: null }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "video[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["video", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
-    hard_break: {
-        inline: true,
-        group: "inline",
-        selectable: false,
-        parseDOM: [{ tag: "br" }],
-        toDOM() { return brDOM; }
-    },
-
-    ordered_list: {
-        ...orderedList,
-        content: 'list_item+',
-        group: 'block',
-        attrs: {
-            bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" },
-            setFontSize: { default: undefined },
-            setFontFamily: { default: "inherit" },
-            setFontColor: { default: "inherit" },
-            inheritedFontSize: { default: undefined },
-            visibility: { default: true },
-            indent: { default: undefined }
-        },
-        toDOM(node: Node<any>) {
-            if (node.attrs.mapStyle === "bullet") return ['ul', 0];
-            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
-            const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
-            const ffam = node.attrs.setFontFamily;
-            const color = node.attrs.setFontColor;
-            return node.attrs.visibility ?
-                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
-                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
-        }
-    },
-
-    bullet_list: {
-        ...bulletList,
-        content: 'list_item+',
-        group: 'block',
-        // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
-        toDOM(node: Node<any>) {
-            return ['ul', 0];
-        }
-    },
-
-    list_item: {
-        attrs: {
-            bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" },
-            visibility: { default: true }
-        },
-        ...listItem,
-        content: 'paragraph block*',
-        toDOM(node: any) {
-            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
-            return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
-            //return ["li", { class: `${map}` }, 0];
-        }
-    },
-};
\ No newline at end of file
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
deleted file mode 100644
index 269423482..000000000
--- a/src/client/util/prosemirrorPatches.js
+++ /dev/null
@@ -1,139 +0,0 @@
-'use strict';
-
-Object.defineProperty(exports, '__esModule', { value: true });
-
-var prosemirrorInputRules = require('prosemirror-inputrules');
-var prosemirrorTransform = require('prosemirror-transform');
-var prosemirrorModel = require('prosemirror-model');
-
-exports.liftListItem = liftListItem;
-exports.sinkListItem = sinkListItem;
-exports.wrappingInputRule = wrappingInputRule;
-// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
-// Create a command to lift the list item around the selection up into
-// a wrapping list.
-function liftListItem(itemType) {
-    return function (tx, dispatch) {
-        var ref = tx.selection;
-        var $from = ref.$from;
-        var $to = ref.$to;
-        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
-        if (!range) { return false }
-        if (!dispatch) { return true }
-        if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
-        { return liftToOuterList(tx, dispatch, itemType, range) }
-        else // Outer list node
-        { return liftOutOfList(tx, dispatch, range) }
-    }
-}
-
-function liftToOuterList(tr, dispatch, itemType, range) {
-    var end = range.end, endOfList = range.$to.end(range.depth);
-    if (end < endOfList) {
-        // There are siblings after the lifted items, which must become
-        // children of the last item
-        tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList,
-            new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
-        range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
-    }
-    dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView());
-    return true
-}
-
-function liftOutOfList(tr, dispatch, range) {
-    var list = range.parent;
-    // Merge the list items into a single big item
-    for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
-        pos -= list.child(i).nodeSize;
-        tr.delete(pos - 1, pos + 1);
-    }
-    var $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
-    var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
-    var parent = $start.node(-1), indexBefore = $start.index(-1);
-    if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
-        item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false }
-    var start = $start.pos, end = start + item.nodeSize;
-    // Strip off the surrounding list. At the sides where we're not at
-    // the end of the list, the existing list is closed. At sides where
-    // this is the end, it is overwritten to its end.
-    tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
-        new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty)))
-            .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))),
-            atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
-    dispatch(tr.scrollIntoView());
-    return true
-}
-
-// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
-// Create a command to sink the list item around the selection down
-// into an inner list.
-function sinkListItem(itemType) {
-    return function (state, dispatch) {
-        var ref = state.selection;
-        var $from = ref.$from;
-        var $to = ref.$to;
-        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
-        if (!range) { return false }
-        var startIndex = range.startIndex;
-        if (startIndex == 0) { return false }
-        var parent = range.parent, nodeBefore = parent.child(startIndex - 1);
-        if (nodeBefore.type != itemType) { return false; }
-
-        if (dispatch) {
-            var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
-            var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
-            let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))),
-                nestedBefore ? 3 : 1, 0);
-            var before = range.start, after = range.end;
-            dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
-                before, after, slice, 1, true))
-                .scrollIntoView());
-        }
-        return true
-    }
-}
-
-function findWrappingOutside(range, type) {
-    var parent = range.parent;
-    var startIndex = range.startIndex;
-    var endIndex = range.endIndex;
-    var around = parent.contentMatchAt(startIndex).findWrapping(type);
-    if (!around) { return null }
-    var outer = around.length ? around[0] : type;
-    return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
-}
-
-function findWrappingInside(range, type) {
-    var parent = range.parent;
-    var startIndex = range.startIndex;
-    var endIndex = range.endIndex;
-    var inner = parent.child(startIndex);
-    var inside = type.contentMatch.findWrapping(inner.type);
-    if (!inside) { return null }
-    var lastType = inside.length ? inside[inside.length - 1] : type;
-    var innerMatch = lastType.contentMatch;
-    for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); }
-    if (!innerMatch || !innerMatch.validEnd) { return null }
-    return inside
-}
-function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) {
-    if (innerRange === void 0) innerRange = range;
-    let withAttrs = (type) => ({ type: type, attrs: null });
-    var around = findWrappingOutside(range, nodeType);
-    var inner = around && findWrappingInside(innerRange, nodeType);
-    if (!inner) { return null }
-    return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs))
-}
-function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) {
-    return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) {
-        var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
-        var tr = state.tr.delete(start, end);
-        var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs);
-        if (!wrapping) { return null }
-        tr.wrap(range, wrapping);
-        var before = tr.doc.resolve(start - 1).nodeBefore;
-        if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) &&
-            (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
-        return tr
-    })
-}
\ No newline at end of file
diff --git a/src/client/util/schema_rts.ts b/src/client/util/schema_rts.ts
deleted file mode 100644
index 83561073c..000000000
--- a/src/client/util/schema_rts.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Schema, Slice } from "prosemirror-model";
-
-import { nodes } from "./nodes_rts";
-import { marks } from "./marks_rts";
-
-
-// :: Schema
-// This schema rougly corresponds to the document schema used by
-// [CommonMark](http://commonmark.org/), minus the list elements,
-// which are defined in the [`prosemirror-schema-list`](#schema-list)
-// module.
-//
-// To reuse elements from this schema, extend or read from its
-// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
-
-export const schema = new Schema({ nodes, marks });
-
-const fromJson = schema.nodeFromJSON;
-
-schema.nodeFromJSON = (json: any) => {
-    const node = fromJson(json);
-    if (json.type === schema.nodes.summary.name) {
-        node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
-    }
-    return node;
-};
\ No newline at end of file
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c02f79187..3624cdb6d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -15,7 +15,7 @@ import './collections/ParentDocumentSelector.scss';
 import './DocumentButtonBar.scss';
 import { LinkMenu } from "./linking/LinkMenu";
 import { DocumentView } from './nodes/DocumentView';
-import { GoogleRef } from "./nodes/FormattedTextBox";
+import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 172c1864a..70ea955e1 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -8,7 +8,7 @@ import { Scripting } from "../util/Scripting";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import GestureOverlay from "./GestureOverlay";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
 
 export class InkingControl {
     @observable static Instance: InkingControl;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8fb67c435..20238985d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -19,7 +19,7 @@ import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { HistoryUtil } from '../util/History';
-import RichTextMenu from '../util/RichTextMenu';
+import RichTextMenu from './nodes/formattedText/RichTextMenu';
 import { Scripting } from '../util/Scripting';
 import SettingsManager from '../util/SettingsManager';
 import SharingManager from '../util/SharingManager';
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index f4250e96d..eda8e5684 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -11,7 +11,7 @@ import "./CollectionCarouselView.scss";
 import { CollectionSubView } from './CollectionSubView';
 import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
 import { Doc } from '../../../new_fields/Doc';
-import { FormattedTextBox } from '../nodes/FormattedTextBox';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 49abc6ee6..fb7535d9f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -15,7 +15,7 @@ import { DragManager, dropActionType } from "../../util/DragManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
 import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { basename } from 'path';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 28b461313..92b27a0c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,7 @@ import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingControl } from "../../InkingControl";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionDockingView } from "../CollectionDockingView";
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index cd8166309..2d3bb6f3c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,7 +19,7 @@ import React = require("react");
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { RichTextField } from "../../../../new_fields/RichTextField";
 import { CollectionView } from "../CollectionView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 
 interface MarqueeViewProps {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index cd78ac7b3..4d20d3e2c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,7 +19,7 @@ import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FontIconBox } from "./FontIconBox";
 import { FieldView, FieldViewProps } from "./FieldView";
-import { FormattedTextBox } from "./FormattedTextBox";
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
 import { ImageBox } from "./ImageBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
deleted file mode 100644
index 3bedb7127..000000000
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ /dev/null
@@ -1,265 +0,0 @@
-@import "../globalCssVariables";
-
-.ProseMirror {
-    width: 100%;
-    height: 100%;
-    min-height: 100%;
-}
-
-.ProseMirror:focus {
-    outline: none !important;
-}
-
-.formattedTextBox-cont {
-    touch-action: none;
-    cursor: text;
-    background: inherit;
-    padding: 0;
-    border-width: 0px;
-    border-radius: inherit;
-    border-color: $intermediate-color;
-    box-sizing: border-box;
-    background-color: inherit;
-    border-style: solid;
-    overflow-y: auto;
-    overflow-x: hidden;
-    color: initial;
-    max-height: 100%;
-    display: flex;
-    flex-direction: row;
-    transition: opacity 1s;
-
-    .formattedTextBox-dictation {
-        height: 12px;
-        width: 10px;
-        top: 0px;
-        left: 0px;
-        position: absolute;
-    }
-}
-.formattedTextBox-outer {
-    position: relative;
-    overflow: auto;
-    display: inline-block;
-    width: 100%;
-    height: 100%;
-}
-
-.formattedTextBox-sidebar-handle {
-    position: absolute;
-    top: calc(50% - 17.5px);
-    width: 10px;
-    height: 35px;
-    background: lightgray;
-    border-radius: 20px;
-    cursor:grabbing;
-}
-
-.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
-    right: 0;
-    left: unset;
-}
-
-.formattedTextBox-sidebar,
-.formattedTextBox-sidebar-inking {
-    border-left: dashed 1px black;
-    height: 100%;
-    display: inline-block;
-    position: absolute;
-    right: 0;
-
-    .collectionfreeformview-container {
-        position: relative;
-    }
-    
-    >.formattedTextBox-sidebar-handle {
-        right: unset;
-        left: -5;
-    }
-}
-
-.formattedTextBox-sidebar-inking {
-    pointer-events: all;
-}
-
-.formattedTextBox-inner-rounded {
-    height: 70%;
-    width: 85%;
-    position: absolute;
-    overflow: auto;
-    top: 15%;
-    left: 10%;
-}
-
-.formattedTextBox-inner-rounded,
-.formattedTextBox-inner {
-    height: 100%;
-    white-space: pre-wrap; 
-}
-
-// .menuicon {
-//     display: inline-block;
-//     border-right: 1px solid rgba(0, 0, 0, 0.2);
-//     color: #888;
-//     line-height: 1;
-//     padding: 0 7px;
-//     margin: 1px;
-//     cursor: pointer;
-//     text-align: center;
-//     min-width: 1.4em;
-// }
-
-.strong,
-.heading {
-    font-weight: bold;
-}
-
-.em {
-    font-style: italic;
-}
-
-.userMarkOpen {
-    background: rgba(255, 255, 0, 0.267);
-    display: inline;
-}
-
-.userMark {
-    background: rgba(255, 255, 0, 0.267);
-    font-size: 2px;
-    display: inline-grid;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    width: 10px;
-    min-height: 10px;
-    text-align: center;
-    align-content: center;
-}
-
-footnote {
-    display: inline-block;
-    position: relative;
-    cursor: pointer;
-
-    div {
-        padding: 0 !important;
-    }
-}
-
-footnote::after {
-    content: counter(prosemirror-footnote);
-    vertical-align: super;
-    font-size: 75%;
-    counter-increment: prosemirror-footnote;
-}
-
-.ProseMirror {
-    counter-reset: prosemirror-footnote;
-}
-
-.footnote-tooltip {
-    cursor: auto;
-    font-size: 75%;
-    position: absolute;
-    left: -30px;
-    top: calc(100% + 10px);
-    background: silver;
-    padding: 3px;
-    border-radius: 2px;
-    max-width: 100px;
-    min-width: 50px;
-    width: max-content;
-}
-
-.prosemirror-attribution {
-    font-size: 8px;
-}
-
-.footnote-tooltip::before {
-    border: 5px solid silver;
-    border-top-width: 0px;
-    border-left-color: transparent;
-    border-right-color: transparent;
-    position: absolute;
-    top: -5px;
-    left: 27px;
-    content: " ";
-    height: 0;
-    width: 0;
-}
-
-
-.formattedTextBox-inlineComment {
-    position: relative;
-    width: 40px;
-    height: 20px;
-    &::before {
-        content: "→";
-    }
-    &:hover {
-        background: orange;
-    }
-}
-
-.formattedTextBox-summarizer {
-    opacity: 0.5;
-    position: relative;
-    width: 40px;
-    height: 20px;
-    &::after {
-        content: "←";
-    }
-}
-
-.formattedTextBox-summarizer-collapsed {
-    opacity: 0.5;
-    position: relative;
-    width: 40px;
-    height: 20px;
-    &::after {
-        content: "...";
-    }
-}
-
-.ProseMirror {
-    touch-action: none;
-    span {
-        font-family: inherit;
-    }
-
-    ol, ul {
-        counter-reset: deci1 0 multi1 0;
-        padding-left: 1em;
-        font-family: inherit;
-    }
-    ol {
-        margin-left: 1em;
-        font-family: inherit;
-    }
-
-    .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
-    .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
-    .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
-    .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
-    .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
-    .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
-
-    .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
-    .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
-    .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
-
-    .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
-    .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
-    .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
-    .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
-    .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
-    .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
-    .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
-    
-    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
-    .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
-    .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
-    .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
deleted file mode 100644
index e65453aa0..000000000
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ /dev/null
@@ -1,1306 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { baseKeymap } from "prosemirror-commands";
-import { history } from "prosemirror-history";
-import { inputRules } from 'prosemirror-inputrules';
-import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
-import { ReplaceStep } from 'prosemirror-transform';
-import { EditorView } from "prosemirror-view";
-import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { InkTool } from '../../../new_fields/InkField';
-import { PrefetchProxy } from '../../../new_fields/Proxy';
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { RichTextUtils } from '../../../new_fields/RichTextUtils';
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../Utils';
-import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { DictationManager } from '../../util/DictationManager';
-import { DragManager } from "../../util/DragManager";
-import { makeTemplate } from '../../util/DropConverter';
-import buildKeymap from "../../util/ProsemirrorExampleTransfer";
-import RichTextMenu from '../../util/RichTextMenu';
-import { RichTextRules } from "../../util/RichTextRules";
-// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "../../util/RichTextSchema";
-// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "../../util/RichTextSchema";
-import { OrderedListView } from "../../util/RichTextSchema";
-import { ImageResizeView } from "../../util/ImageResizeView";
-
-import { DashDocCommentView } from "../../util/DashDocCommentView";
-import { DashFieldView } from "../../util/DashFieldView";
-import { FootnoteView } from "../../util/FootnoteView";
-import { SummaryView } from "../../util/SummaryView";
-import { DashDocView } from "../../util/DashDocView";
-
-import { schema } from "../../util/schema_rts";
-import { SelectionManager } from "../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { DocumentButtonBar } from '../DocumentButtonBar';
-import { InkingControl } from "../InkingControl";
-import { AudioBox } from './AudioBox';
-import { FieldView, FieldViewProps } from "./FieldView";
-import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
-import React = require("react");
-
-library.add(faEdit);
-library.add(faSmile, faTextHeight, faUpload);
-
-export interface FormattedTextBoxProps {
-    hideOnLeave?: boolean;
-    makeLink?: () => Opt<Doc>;
-    xMargin?: number;
-    yMargin?: number;
-}
-
-const richTextSchema = createSchema({
-    documentText: "string"
-});
-
-export const GoogleRef = "googleDocId";
-
-type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
-const RichTextDocument = makeInterface(richTextSchema, documentSchema);
-
-type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
-
-@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
-    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
-    public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
-    public static Instance: FormattedTextBox;
-    public ProseRef?: HTMLDivElement;
-    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
-    private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _editorView: Opt<EditorView>;
-    private _applyingChange: boolean = false;
-    private _searchIndex = 0;
-    private _sidebarMovement = 0;
-    private _lastX = 0;
-    private _lastY = 0;
-    private _undoTyping?: UndoManager.Batch;
-    private _disposers: { [name: string]: IReactionDisposer } = {};
-    private dropDisposer?: DragManager.DragDropDisposer;
-
-    @computed get _recording() { return this.dataDoc.audioState === "recording"; }
-    set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
-
-    @observable private _entered = false;
-
-    public static FocusedBox: FormattedTextBox | undefined;
-    public static SelectOnLoad = "";
-    public static SelectOnLoadChar = "";
-    public static IsFragment(html: string) {
-        return html.indexOf("data-pm-slice") !== -1;
-    }
-    public static GetHref(html: string): string {
-        const parser = new DOMParser();
-        const parsedHtml = parser.parseFromString(html, 'text/html');
-        if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
-            (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
-            return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
-        }
-        return "";
-    }
-    public static GetDocFromUrl(url: string) {
-        if (url.startsWith(document.location.origin)) {
-            const split = new URL(url).pathname.split("doc/");
-            const docid = split[split.length - 1];
-            return docid;
-        }
-        return "";
-    }
-
-    @undoBatch
-    public setFontColor(color: string) {
-        const view = this._editorView!;
-        if (view.state.selection.from === view.state.selection.to) return false;
-        if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
-            this.layoutDoc.color = color;
-        }
-        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
-        view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
-        return true;
-    }
-
-    constructor(props: any) {
-        super(props);
-        FormattedTextBox.Instance = this;
-        this.updateHighlights();
-    }
-
-    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
-
-    linkOnDeselect: Map<string, string> = new Map();
-
-    doLinkOnDeselect() {
-        Array.from(this.linkOnDeselect.entries()).map(entry => {
-            const key = entry[0];
-            const value = entry[1];
-            const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-            DocServer.GetRefField(value).then(doc => {
-                DocServer.GetRefField(id).then(linkDoc => {
-                    this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
-                    DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
-                    if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
-                    else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
-                });
-            });
-        });
-        this.linkOnDeselect.clear();
-    }
-
-    dispatchTransaction = (tx: Transaction) => {
-        if (this._editorView) {
-            const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
-            if (metadata) {
-                const range = tx.selection.$from.blockRange(tx.selection.$to);
-                let text = range ? tx.doc.textBetween(range.start, range.end) : "";
-                let textEndSelection = tx.selection.to;
-                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { }
-                text = text.substr(0, textEndSelection - range!.start);
-                text = text.split(" ")[text.split(" ").length - 1];
-                const split = text.split("::");
-                if (split.length > 1 && split[1]) {
-                    const key = split[0];
-                    const value = split[split.length - 1];
-                    this.linkOnDeselect.set(key, value);
-
-                    const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-                    const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", 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);
-                    this.dataDoc[key] = value;
-                }
-            }
-            const state = this._editorView.state.apply(tx);
-            this._editorView.updateState(state);
-            (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
-
-            const tsel = this._editorView.state.selection.$from;
-            tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
-            const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
-            const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
-            if (!this._applyingChange) {
-                this._applyingChange = true;
-                this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-                if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                    this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText);
-                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
-                } else { // if we've deleted all the text in a note driven by a template, then restore the template data
-                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
-                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
-                }
-                this._applyingChange = false;
-            }
-            this.updateTitle();
-            this.tryUpdateHeight();
-        }
-    }
-
-    updateTitle = () => {
-        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
-            const str = this._editorView.state.doc.textContent;
-            const titlestr = str.substr(0, Math.min(40, str.length));
-            this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
-        }
-    }
-
-    // needs a better API for taking in a set of words with target documents instead of just one target
-    public hyperlinkTerms = (terms: string[], target: Doc) => {
-        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            const tr = this._editorView.state.tr;
-            const flattened: TextSelection[] = [];
-            res.map(r => r.map(h => flattened.push(h)));
-            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
-            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-            const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!;
-            const link = this._editorView.state.schema.marks.link.create({
-                href: Utils.prepend("/doc/" + alink[Id]),
-                title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
-            });
-            this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
-        }
-    }
-    public highlightSearchTerms = (terms: string[]) => {
-        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            let tr = this._editorView.state.tr;
-            const flattened: TextSelection[] = [];
-            res.map(r => r.map(h => flattened.push(h)));
-            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
-            flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
-            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-            this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
-        }
-    }
-
-    public unhighlightSearchTerms = () => {
-        if (this._editorView && (this._editorView as any).docView) {
-            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const end = this._editorView.state.doc.nodeSize - 2;
-            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
-        }
-    }
-    adoptAnnotation = (start: number, end: number, mark: Mark) => {
-        const view = this._editorView!;
-        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
-        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
-    }
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        this.ProseRef = ele;
-        this.dropDisposer?.();
-        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
-    }
-
-    @undoBatch
-    @action
-    drop = async (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
-            // replace text contents whend dragging with Alt
-            if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
-                if (draggedDoc.data instanceof RichTextField) {
-                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
-                    e.stopPropagation();
-                }
-                // embed document when dragging with a userDropAction or an embedDoc flag set
-            } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
-                const target = de.complete.docDragData.droppedDocuments[0];
-                // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
-                // if (link) {
-                target._fitToBox = true;
-                const node = schema.nodes.dashDoc.create({
-                    width: target[WidthSym](), height: target[HeightSym](),
-                    title: "dashDoc", docid: target[Id],
-                    float: "right"
-                });
-                const view = this._editorView!;
-                view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
-                this.tryUpdateHeight();
-                e.stopPropagation();
-                // }
-            } // otherwise, fall through to outer collection to handle drop
-        } else if (de.complete.linkDragData) {
-            de.complete.linkDragData.linkDropCallback = this.linkDrop;
-        }
-    }
-    linkDrop = (data: DragManager.LinkDragData) => {
-        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);
-    }
-
-    getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
-        let offset = 0;
-
-        if (context === node) return { from: offset, to: offset + node.nodeSize };
-
-        if (node.isBlock) {
-            // tslint:disable-next-line: prefer-for-of
-            for (let i = 0; i < (context.content as any).content.length; i++) {
-                const result = this.getNodeEndpoints((context.content as any).content[i], node);
-                if (result) {
-                    return {
-                        from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
-                        to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
-                    };
-                }
-                offset += (context.content as any).content[i].nodeSize;
-            }
-            return null;
-        } else {
-            return null;
-        }
-    }
-
-
-    //Recursively finds matches within a given node
-    findInNode(pm: EditorView, node: Node, find: string) {
-        let ret: TextSelection[] = [];
-
-        if (node.isTextblock) {
-            let index = 0, foundAt;
-            const ep = this.getNodeEndpoints(pm.state.doc, node);
-            while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
-                const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
-                ret.push(sel);
-                index = index + foundAt + find.length;
-            }
-        } else {
-            node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
-        }
-        return ret;
-    }
-    static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
-
-    updateHighlights = () => {
-        clearStyleSheetRules(FormattedTextBox._userStyleSheet);
-        if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
-        }
-        if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
-        }
-        if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
-            const min = Math.round(Date.now() / 1000 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
-            setTimeout(() => this.updateHighlights());
-        }
-        if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
-            const hr = Math.round(Date.now() / 1000 / 60 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
-        }
-    }
-
-    sidebarDown = (e: React.PointerEvent) => {
-        this._lastX = e.clientX;
-        this._lastY = e.clientY;
-        this._sidebarMovement = 0;
-        document.addEventListener("pointermove", this.sidebarMove);
-        document.addEventListener("pointerup", this.sidebarUp);
-        e.stopPropagation();
-        e.preventDefault(); // prevents text from being selected during drag
-    }
-    sidebarMove = (e: PointerEvent) => {
-        const bounds = this.CurrentDiv.getBoundingClientRect();
-        this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
-        this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
-    }
-    sidebarUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.sidebarMove);
-        document.removeEventListener("pointerup", this.sidebarUp);
-    }
-
-    toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
-
-    public static get DefaultLayout(): Doc | string | undefined {
-        return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
-    }
-    specificContextMenu = (e: React.MouseEvent): void => {
-        const cm = ContextMenu.Instance;
-
-        const funcs: ContextMenuProps[] = [];
-        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
-        funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-        !this.props.Document.rootDocument && funcs.push({
-            description: "Make Template", event: () => {
-                this.props.Document.isTemplateDoc = makeTemplate(this.props.Document);
-                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
-            }, icon: "eye"
-        });
-        funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
-
-        const highlighting: ContextMenuProps[] = [];
-        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
-            highlighting.push({
-                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
-                    e.stopPropagation();
-                    if (FormattedTextBox._highlights.indexOf(option) === -1) {
-                        FormattedTextBox._highlights.push(option);
-                    } else {
-                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
-                    }
-                    this.updateHighlights();
-                }, icon: "expand-arrows-alt"
-            }));
-        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
-
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
-        const change = cm.findByDescription("Change Perspective...");
-        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
-
-        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
-        DocListCast(noteTypesDoc?.data).forEach(note => {
-            changeItems.push({
-                description: StrCast(note.title), event: undoBatch(() => {
-                    Doc.setNativeView(this.props.Document);
-                    Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
-                }), icon: "eye"
-            });
-        });
-        changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
-        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
-
-        const open = cm.findByDescription("Add a Perspective...");
-        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-
-        openItems.push({
-            description: "FreeForm", event: undoBatch(() => {
-                const alias = Doc.MakeAlias(this.rootDoc);
-                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
-                this.props.addDocTab(alias, "onRight");
-            }), icon: "eye"
-        });
-        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
-
-    }
-
-    recordDictation = () => {
-        DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
-            continuous: { indefinite: false },
-        }).then(results => {
-            if (results && [DictationManager.Controls.Infringed].includes(results)) {
-                DictationManager.Controls.stop();
-            }
-            //this._editorView!.focus();
-        });
-    }
-    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
-
-    @action
-    toggleMenubar = () => {
-        this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
-    }
-
-    recordBullet = async () => {
-        const completedCue = "end session";
-        const results = await DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
-            continuous: { indefinite: false },
-            terminators: [completedCue, "bullet", "next"]
-        });
-        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
-            DictationManager.Controls.stop();
-            return;
-        }
-        this.nextBullet(this._editorView!.state.selection.to);
-        setTimeout(this.recordBullet, 2000);
-    }
-
-    setCurrentBulletContent = (value: string) => {
-        if (this._editorView) {
-            const state = this._editorView.state;
-            const now = Date.now();
-            let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
-            if (!this._break && state.selection.to !== state.selection.from) {
-                for (let i = state.selection.from; i <= state.selection.to; i++) {
-                    const pos = state.doc.resolve(i);
-                    const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
-                    if (um) {
-                        mark = um;
-                        break;
-                    }
-                }
-            }
-            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
-            this._break = false;
-            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
-            const from = state.selection.from;
-            const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
-            this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
-        }
-    }
-
-    nextBullet = (pos: number) => {
-        if (this._editorView) {
-            const frag = Fragment.fromArray(this.newListItems(2));
-            if (this._editorView.state.doc.resolve(pos).depth >= 2) {
-                const slice = new Slice(frag, 2, 2);
-                let state = this._editorView.state;
-                this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
-                pos += 4;
-                state = this._editorView.state;
-                this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
-            }
-        }
-    }
-
-    private newListItems = (count: number) => {
-        return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
-    }
-
-    _keymap: any = undefined;
-    _rules: RichTextRules | undefined;
-    @computed get config() {
-        this._keymap = buildKeymap(schema, this.props);
-        this._rules = new RichTextRules(this.props.Document, this);
-        return {
-            schema,
-            plugins: [
-                inputRules(this._rules.inpRules),
-                this.richTextMenuPlugin(),
-                history(),
-                keymap(this._keymap),
-                keymap(baseKeymap),
-                new Plugin({
-                    props: {
-                        attributes: { class: "ProseMirror-example-setup-style" }
-                    }
-                }),
-                formattedTextBoxCommentPlugin
-            ]
-        };
-    }
-
-    makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) {
-        if (this._editorView) {
-            const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
-            this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link).
-                addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link));
-        }
-    }
-    componentDidMount() {
-        this._disposers.buttonBar = reaction(
-            () => DocumentButtonBar.Instance,
-            instance => {
-                if (instance) {
-                    this.pullFromGoogleDoc(this.checkState);
-                    this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true);
-                }
-            }
-        );
-        this._disposers.linkMaker = reaction(
-            () => this.props.makeLink?.(),
-            (linkDoc: Opt<Doc>) => {
-                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);
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.editorState = reaction(
-            () => {
-                if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
-                    return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
-                }
-                return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
-            },
-            incomingValue => {
-                if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
-                    const updatedState = JSON.parse(incomingValue);
-                    this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
-                    this.tryUpdateHeight();
-                }
-            }
-        );
-        this._disposers.pullDoc = reaction(
-            () => this.props.Document[Pulls],
-            () => {
-                if (!DocumentButtonBar.hasPulledHack) {
-                    DocumentButtonBar.hasPulledHack = true;
-                    const unchanged = this.dataDoc.unchanged;
-                    this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
-                }
-            }
-        );
-        this._disposers.pushDoc = reaction(
-            () => this.props.Document[Pushes],
-            () => {
-                if (!DocumentButtonBar.hasPushedHack) {
-                    DocumentButtonBar.hasPushedHack = true;
-                    this.pushToGoogleDoc();
-                }
-            }
-        );
-        this._disposers.height = reaction(
-            () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
-            () => this.tryUpdateHeight()
-        );
-
-        this.setupEditor(this.config, this.props.fieldKey);
-
-        this._disposers.search = reaction(() => this.rootDoc.searchMatch,
-            search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
-            { fireImmediately: true });
-
-        this._disposers.record = reaction(() => this._recording,
-            () => {
-                if (this._recording) {
-                    setTimeout(action(() => {
-                        this.stopDictation(true);
-                        setTimeout(() => this.recordDictation(), 500);
-                    }), 500);
-                } else setTimeout(() => this.stopDictation(true), 0);
-            }
-        );
-        this._disposers.scrollToRegion = reaction(
-            () => StrCast(this.layoutDoc.scrollToLinkID),
-            async (scrollToLinkID) => {
-                const findLinkFrag = (frag: Fragment, editor: EditorView) => {
-                    const nodes: Node[] = [];
-                    frag.forEach((node, index) => {
-                        const examinedNode = findLinkNode(node, editor);
-                        if (examinedNode && examinedNode.textContent) {
-                            nodes.push(examinedNode);
-                            start += index;
-                        }
-                    });
-                    return { frag: Fragment.fromArray(nodes), start: start };
-                };
-                const findLinkNode = (node: Node, editor: EditorView) => {
-                    if (!node.isText) {
-                        const content = findLinkFrag(node.content, editor);
-                        return node.copy(content.frag);
-                    }
-                    const marks = [...node.marks];
-                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
-                    return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
-                };
-
-                let start = -1;
-                if (this._editorView && scrollToLinkID) {
-                    const editor = this._editorView;
-                    const ret = findLinkFrag(editor.state.doc.content, editor);
-
-                    if (ret.frag.size > 2 && ret.start >= 0) {
-                        let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
-                        if (ret.frag.firstChild) {
-                            selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
-                        }
-                        editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
-                        const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-                        setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
-                        setTimeout(() => this.unhighlightSearchTerms(), 2000);
-                    }
-                    Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
-                }
-
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos),
-            pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
-        );
-
-        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
-    }
-
-    pushToGoogleDoc = async () => {
-        this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-            const modes = GoogleApiClientUtils.Docs.WriteMode;
-            let mode = modes.Replace;
-            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
-            if (!reference) {
-                mode = modes.Insert;
-                reference = { title: StrCast(this.dataDoc.title) };
-            }
-            const redo = async () => {
-                if (this._editorView && reference) {
-                    const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
-                    const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                    response && (this.dataDoc[GoogleRef] = response.documentId);
-                    const pushSuccess = response !== undefined && !("errors" in response);
-                    dataDoc.unchanged = pushSuccess;
-                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
-                }
-            };
-            const undo = () => {
-                if (!exportState) {
-                    return;
-                }
-                const content: GoogleApiClientUtils.Docs.Content = {
-                    text: exportState.text,
-                    requests: []
-                };
-                if (reference && content) {
-                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                }
-            };
-            UndoManager.AddEvent({ undo, redo });
-            redo();
-        });
-    }
-
-    pullFromGoogleDoc = async (handler: PullHandler) => {
-        const dataDoc = this.dataDoc;
-        const documentId = StrCast(dataDoc[GoogleRef]);
-        let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
-        if (documentId) {
-            exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
-        }
-        UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
-    }
-
-    updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        let pullSuccess = false;
-        if (exportState !== undefined) {
-            pullSuccess = true;
-            dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
-            setTimeout(() => {
-                if (this._editorView) {
-                    const state = this._editorView.state;
-                    const end = state.doc.content.size - 1;
-                    this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
-                }
-            }, 0);
-            dataDoc.title = exportState.title;
-            this.rootDoc.customTitle = true;
-            dataDoc.unchanged = true;
-        } else {
-            delete dataDoc[GoogleRef];
-        }
-        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
-    }
-
-    checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        if (exportState && this._editorView) {
-            const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
-            const equalTitles = dataDoc.title === exportState.title;
-            const unchanged = equalContent && equalTitles;
-            dataDoc.unchanged = unchanged;
-            DocumentButtonBar.Instance.setPullState(unchanged);
-        }
-    }
-
-    clipboardTextSerializer = (slice: Slice): string => {
-        let text = "", separated = true;
-        const from = 0, to = slice.content.size;
-        slice.content.nodesBetween(from, to, (node, pos) => {
-            if (node.isText) {
-                text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
-                separated = false;
-            } else if (!separated && node.isBlock) {
-                text += "\n";
-                separated = true;
-            } else if (node.type.name === "hard_break") {
-                text += "\n";
-            }
-        }, 0);
-        return text;
-    }
-
-    sliceSingleNode(slice: Slice) {
-        return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null;
-    }
-
-    handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
-        const cbe = event as ClipboardEvent;
-        const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
-        const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
-        if (pdfDocId && pdfRegionId) {
-            DocServer.GetRefField(pdfDocId).then(pdfDoc => {
-                DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
-                    if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
-                        setTimeout(async () => {
-                            const targetField = Doc.LayoutFieldKey(pdfDoc);
-                            const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
-                            targetAnnotations?.push(pdfRegion);
-                        });
-
-                        const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted");
-                        if (link) {
-                            cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
-                            const linkId = link[Id];
-                            const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
-                            slice = new Slice(frag, slice.openStart, slice.openEnd);
-                            const tr = view.state.tr.replaceSelection(slice);
-                            view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
-                        }
-                    }
-                });
-            });
-            return true;
-        }
-        return false;
-
-
-        function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
-            const nodes: Node[] = [];
-            frag.forEach(node => nodes.push(marker(node)));
-            return Fragment.fromArray(nodes);
-        }
-        function addLinkMark(node: Node, title: string, linkId: string) {
-            if (!node.isText) {
-                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
-                return node.copy(content);
-            }
-            const marks = [...node.marks];
-            const linkIndex = marks.findIndex(mark => mark.type.name === "link");
-            const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
-            marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
-            return node.mark(marks);
-        }
-    }
-
-    private setupEditor(config: any, fieldKey: string) {
-        const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
-        const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"];
-        const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
-        if (this.ProseRef) {
-            const self = this;
-            this._editorView?.destroy();
-            this._editorView = new EditorView(this.ProseRef, {
-                state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
-                handleScrollToSelection: (editorView) => {
-                    const docPos = editorView.coordsAtPos(editorView.state.selection.from);
-                    const viewRect = self._ref.current!.getBoundingClientRect();
-                    if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
-                        docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
-                    }
-                    return true;
-                },
-                dispatchTransaction: this.dispatchTransaction,
-                nodeViews: {
-                    //dashComment(node, view, getPos) { return new DashDocCommentView({ node, view, getPos }); },
-                    dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
-                    //dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
-                    // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); },
-
-                    // image(node, view, getPos) {
-                    //     //const addDocTab = this.props.addDocTab;
-                    //     return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab });
-                    // },
-
-
-                    // // WAS : 
-                    // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); },
-
-                    // summary(node, view, getPos) { return new SummaryView({ node, view, getPos }); },
-                    // ordered_list(node, view, getPos) { return new OrderedListView(); },
-                    // footnote(node, view, getPos) { return new FootnoteView({ node, outerView, getPos }); }
-                },
-                clipboardTextSerializer: this.clipboardTextSerializer,
-                handlePaste: this.handlePaste,
-            });
-            const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
-            if (startupText) {
-                const { state: { tr }, dispatch } = this._editorView;
-                dispatch(tr.insertText(startupText));
-            }
-        }
-
-        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
-        if (selectOnLoad && !this.props.dontRegisterView) {
-            FormattedTextBox.SelectOnLoad = "";
-            this.props.select(false);
-            FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
-            FormattedTextBox.SelectOnLoadChar = "";
-
-        }
-        (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
-        // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
-    }
-    getFont(font: string) {
-        switch (font) {
-            case "Arial": return schema.marks.arial.create();
-            case "Times New Roman": return schema.marks.timesNewRoman.create();
-            case "Georgia": return schema.marks.georgia.create();
-            case "Comic Sans MS": return schema.marks.comicSans.create();
-            case "Tahoma": return schema.marks.tahoma.create();
-            case "Impact": return schema.marks.impact.create();
-            case "ACrimson Textrial": return schema.marks.crimson.create();
-        }
-        return schema.marks.arial.create();
-    }
-
-    componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-        this._editorView?.destroy();
-    }
-
-    static _downEvent: any;
-    _downX = 0;
-    _downY = 0;
-    _break = false;
-    onPointerDown = (e: React.PointerEvent): void => {
-        if (this._recording && !e.ctrlKey && e.button === 0) {
-            this.stopDictation(true);
-            this._break = true;
-            const state = this._editorView!.state;
-            const to = state.selection.to;
-            const updated = TextSelection.create(state.doc, to, to);
-            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
-            e.preventDefault();
-            e.stopPropagation();
-            if (this._recording) setTimeout(() => this.recordDictation(), 500);
-        }
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this.doLinkOnDeselect();
-        FormattedTextBox._downEvent = true;
-        FormattedTextBoxComment.textBox = this;
-        if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
-            e.preventDefault();
-        }
-        if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
-            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
-                e.stopPropagation();
-            }
-        }
-        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
-            e.preventDefault();
-        }
-    }
-
-    onPointerUp = (e: React.PointerEvent): void => {
-        if (!FormattedTextBox._downEvent) return;
-        FormattedTextBox._downEvent = false;
-        if (!(e.nativeEvent as any).formattedHandled) {
-            FormattedTextBoxComment.textBox = this;
-            FormattedTextBoxComment.update(this._editorView!);
-        }
-        (e.nativeEvent as any).formattedHandled = true;
-
-        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
-            e.stopPropagation();
-        }
-        this._downX = this._downY = Number.NaN;
-    }
-
-    @action
-    onFocused = (e: React.FocusEvent): void => {
-        FormattedTextBox.FocusedBox = this;
-        this.tryUpdateHeight();
-
-        // see if we need to preserve the insertion point
-        const prosediv = this.ProseRef?.children?.[0] as any;
-        const keeplocation = prosediv?.keeplocation;
-        prosediv && (prosediv.keeplocation = undefined);
-        const pos = this._editorView?.state.selection.$from.pos || 1;
-        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
-        const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
-
-        // jump rich text menu to this textbox
-        const bounds = this._ref.current?.getBoundingClientRect();
-        if (bounds && this.props.Document._chromeStatus !== "disabled") {
-            const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
-            let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
-            if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
-                y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
-            }
-            RichTextMenu.Instance.jumpTo(x, y);
-        }
-    }
-    onPointerWheel = (e: React.WheelEvent): void => {
-        // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
-        if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
-            e.stopPropagation();
-        }
-    }
-
-    static _bulletStyleSheet: any = addStyleSheet();
-    static _userStyleSheet: any = addStyleSheet();
-
-    onClick = (e: React.MouseEvent): void => {
-        if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
-            const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-            const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
-            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
-                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
-                e.preventDefault();
-            }
-            if (!node && this.ProseRef) {
-                const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
-                if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
-                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
-                }
-            }
-        }
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
-        (e.nativeEvent as any).formattedHandled = true;
-        // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
-        //     let href = (e.target as any).href;
-        //     let location: string;
-        //     if ((e.target as any).attributes.location) {
-        //         location = (e.target as any).attributes.location.value;
-        //     }
-        //     let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-        //     let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
-        //     if (node) {
-        //         let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
-        //         if (link && !(link.attrs.docref && link.attrs.title)) {  // bcz: getting hacky.  this indicates that we clicked on a PDF excerpt quotation.  In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
-        //             href = link && link.attrs.href;
-        //             location = link && link.attrs.location;
-        //         }
-        //     }
-        //     if (href) {
-        //         if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-        //             let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-        //             if (linkClicked) {
-        //                 DocServer.GetRefField(linkClicked).then(async linkDoc => {
-        //                     (linkDoc instanceof Doc) &&
-        //                         DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false);
-        //                 });
-        //             }
-        //         } else {
-        //             let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
-        //             this.props.addDocument && this.props.addDocument(webDoc);
-        //         }
-        //         e.stopPropagation();
-        //         e.preventDefault();
-        //     }
-        // }
-
-        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
-            this.props.select(e.ctrlKey);
-            this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
-        }
-    }
-
-    // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
-    hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
-        clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
-        const pos = this._editorView!.posAtCoords({ left: x, top: y });
-        if (pos && this.props.isSelected(true)) {
-            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
-            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
-            const $pos = this._editorView!.state.doc.resolve(pos.pos);
-            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
-            if ($pos.node().type === schema.nodes.ordered_list) {
-                for (let off = 1; off < 100; off++) {
-                    const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
-                    const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node?.type === schema.nodes.list_item) {
-                        list_node = node;
-                        break;
-                    }
-                }
-            }
-            if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
-                if (select) {
-                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
-                    if (!highlightOnly) {
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
-                    }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                } else if (Math.abs(pos.pos - pos.inside) < 2) {
-                    if (!highlightOnly) {
-                        const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
-                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
-                    }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                }
-            }
-        }
-    }
-    onMouseUp = (e: React.MouseEvent): void => {
-        e.stopPropagation();
-
-        const view = this._editorView as any;
-        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there 
-        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
-        if (view.mouseDown) {
-            const originalUpHandler = view.mouseDown.up;
-            view.root.removeEventListener("mouseup", originalUpHandler);
-            view.mouseDown.up = (e: MouseEvent) => {
-                !(e as any).formattedHandled && originalUpHandler(e);
-                // e.stopPropagation();
-                (e as any).formattedHandled = true;
-            };
-            view.root.addEventListener("mouseup", view.mouseDown.up);
-        }
-    }
-
-    richTextMenuPlugin() {
-        return new Plugin({
-            view(newView) {
-                RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
-                return RichTextMenu.Instance;
-            }
-        });
-    }
-
-    public static HadSelection: boolean = false;
-    onBlur = (e: any) => {
-        FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
-        //DictationManager.Controls.stop(false);
-        if (this._undoTyping) {
-            this._undoTyping.end();
-            this._undoTyping = undefined;
-        }
-        this.doLinkOnDeselect();
-
-        // move the richtextmenu offscreen
-        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
-    }
-
-    _lastTimedMark: Mark | undefined = undefined;
-    onKeyPress = (e: React.KeyboardEvent) => {
-        if (e.altKey) {
-            e.preventDefault();
-            return;
-        }
-        const state = this._editorView!.state;
-        if (!state.selection.empty && e.key === "%") {
-            this._rules!.EnteringStyle = true;
-            e.preventDefault();
-            e.stopPropagation();
-            return;
-        }
-
-        if (state.selection.empty || !this._rules!.EnteringStyle) {
-            this._rules!.EnteringStyle = false;
-        }
-        if (e.key === "Escape") {
-            this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-            (document.activeElement as any).blur?.();
-            SelectionManager.DeselectAll();
-        }
-        e.stopPropagation();
-        if (e.key === "Tab" || e.key === "Enter") {
-            e.preventDefault();
-        }
-        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
-        this._lastTimedMark = mark;
-        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
-
-        if (!this._undoTyping) {
-            this._undoTyping = UndoManager.StartBatch("undoTyping");
-        }
-    }
-
-    onscrolled = (ev: React.UIEvent) => {
-        this.props.Document.scrollPos = this._scrollRef.current!.scrollTop;
-    }
-    @action
-    tryUpdateHeight(limitHeight?: number) {
-        let scrollHeight = this._ref.current?.scrollHeight;
-        if (this.layoutDoc._autoHeight && scrollHeight &&
-            getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
-            if (limitHeight && scrollHeight > limitHeight) {
-                scrollHeight = limitHeight;
-                this.layoutDoc.limitHeight = undefined;
-                this.layoutDoc._autoHeight = false;
-            }
-            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
-            const dh = NumCast(this.layoutDoc._height, 0);
-            const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
-            if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
-                this.layoutDoc._height = newHeight;
-                this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
-            }
-        }
-    }
-
-    @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
-    sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
-    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
-    @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
-    render() {
-        TraceMobx();
-        const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
-        if (this.props.isSelected()) {
-            this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
-        } else if (FormattedTextBoxComment.textBox === this) {
-            FormattedTextBoxComment.Hide();
-        }
-        return (
-
-            <div className={`formattedTextBox-cont`} ref={this._ref}
-                style={{
-                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
-                    background: StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
-                    opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
-                    color: StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
-                    pointerEvents: interactive ? "none" : undefined,
-                    fontSize: Cast(this.layoutDoc._fontSize, "number", null),
-                    fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
-                }}
-                onContextMenu={this.specificContextMenu}
-                onKeyDown={this.onKeyPress}
-                onFocus={this.onFocused}
-                onClick={this.onClick}
-                onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
-                onBlur={this.onBlur}
-                onPointerUp={this.onPointerUp}
-                onPointerDown={this.onPointerDown}
-                onMouseUp={this.onMouseUp}
-                onWheel={this.onPointerWheel}
-                onPointerEnter={action(() => this._entered = true)}
-                onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
-                    this._entered = false;
-                    const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
-                    for (let child: any = target; child; child = child?.parentElement) {
-                        if (child === this._ref.current!) {
-                            this._entered = true;
-                        }
-                    }
-                })}
-            >
-                <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
-                    <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
-                        style={{
-                            padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
-                            pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
-                        }} />
-                </div>
-                {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
-                    <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
-                    <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
-                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                        <CollectionFreeFormView {...this.props}
-                            PanelHeight={this.props.PanelHeight}
-                            PanelWidth={this.sidebarWidth}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
-                            annotationsKey={this.annotationKey}
-                            isAnnotationOverlay={false}
-                            focus={this.props.focus}
-                            isSelected={this.props.isSelected}
-                            select={emptyFunction}
-                            active={this.annotationsActive}
-                            ContentScaling={returnOne}
-                            whenActiveChanged={this.whenActiveChanged}
-                            removeDocument={this.removeDocument}
-                            moveDocument={this.moveDocument}
-                            addDocument={this.addDocument}
-                            CollectionView={undefined}
-                            ScreenToLocalTransform={this.sidebarScreenToLocal}
-                            renderDepth={this.props.renderDepth + 1}
-                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
-                        </CollectionFreeFormView>
-                        <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
-                    </div>}
-                {!this.props.Document._showAudio ? (null) :
-                    <div className="formattedTextBox-dictation"
-                        onPointerDown={e => {
-                            runInAction(() => this._recording = !this._recording);
-                            setTimeout(() => this._editorView!.focus(), 500);
-                            e.stopPropagation();
-                        }} >
-                        <FontAwesomeIcon className="formattedTExtBox-audioFont"
-                            style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
-                    </div>}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
deleted file mode 100644
index 2dd63ec21..000000000
--- a/src/client/views/nodes/FormattedTextBoxComment.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-.FormattedTextBox-tooltip {
-    position: absolute;
-    pointer-events: none;
-    z-index: 20;
-    background: white;
-    border: 1px solid silver;
-    border-radius: 2px;
-    margin-bottom: 7px;
-    -webkit-transform: translateX(-50%);
-    transform: translateX(-50%);
-  }
-  .FormattedTextBox-tooltip:before {
-    content: "";
-    height: 0; width: 0;
-    position: absolute;
-    left: 50%;
-    margin-left: -5px;
-    bottom: -6px;
-    border: 5px solid transparent;
-    border-bottom-width: 0;
-    border-top-color: silver;
-  }
-  .FormattedTextBox-tooltip:after {
-    content: "";
-    height: 0; width: 0;
-    position: absolute;
-    left: 50%;
-    margin-left: -5px;
-    bottom: -4.5px;
-    border: 5px solid transparent;
-    border-bottom-width: 0;
-    border-top-color: white;
-  }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
deleted file mode 100644
index dfea0f6bb..000000000
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocCastAsync } from "../../../new_fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { DocumentManager } from "../../util/DocumentManager";
-import { schema } from "../../util/schema_rts";
-import { Transform } from "../../util/Transform";
-import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
-import { FormattedTextBox } from "./FormattedTextBox";
-import './FormattedTextBoxComment.scss';
-import React = require("react");
-import { Docs } from "../../documents/Documents";
-import wiki from "wikijs";
-import { DocumentType } from "../../documents/DocumentTypes";
-
-export let formattedTextBoxCommentPlugin = new Plugin({
-    view(editorView) { return new FormattedTextBoxComment(editorView); }
-});
-export function findOtherUserMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
-}
-export function findUserMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid);
-}
-export function findLinkMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.type === schema.marks.link);
-}
-export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
-    let before = 0;
-    let nbef = rpos.nodeBefore;
-    while (nbef && finder(nbef.marks)) {
-        before += nbef.nodeSize;
-        rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
-        rpos && (nbef = rpos.nodeBefore);
-    }
-    return before;
-}
-export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
-    let after = 0;
-    let naft = rpos.nodeAfter;
-    while (naft && finder(naft.marks)) {
-        after += naft.nodeSize;
-        rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
-        rpos && (naft = rpos.nodeAfter);
-    }
-    return after;
-}
-
-
-export class FormattedTextBoxComment {
-    static tooltip: HTMLElement;
-    static tooltipText: HTMLElement;
-    static tooltipInput: HTMLInputElement;
-    static start: number;
-    static end: number;
-    static mark: Mark;
-    static textBox: FormattedTextBox | undefined;
-    static linkDoc: Doc | undefined;
-    constructor(view: any) {
-        if (!FormattedTextBoxComment.tooltip) {
-            const root = document.getElementById("root");
-            FormattedTextBoxComment.tooltipInput = document.createElement("input");
-            FormattedTextBoxComment.tooltipInput.type = "checkbox";
-            FormattedTextBoxComment.tooltip = document.createElement("div");
-            FormattedTextBoxComment.tooltipText = document.createElement("div");
-            FormattedTextBoxComment.tooltipText.style.width = "100%";
-            FormattedTextBoxComment.tooltipText.style.height = "100%";
-            FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
-            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
-            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
-            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
-            FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
-            FormattedTextBoxComment.tooltip.style.width = "100%";
-            FormattedTextBoxComment.tooltip.style.height = "100%";
-            FormattedTextBoxComment.tooltip.style.overflow = "hidden";
-            FormattedTextBoxComment.tooltip.style.display = "none";
-            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
-            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
-                const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
-                const textBox = FormattedTextBoxComment.textBox;
-                if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
-                    if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
-                    } else {
-                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                            (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : 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 }), "onRight");
-                }
-                keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
-                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
-                e.stopPropagation();
-                e.preventDefault();
-            };
-            root && root.appendChild(FormattedTextBoxComment.tooltip);
-        }
-    }
-
-    public static Hide() {
-        FormattedTextBoxComment.textBox = undefined;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
-        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-    }
-    public static SetState(textBox: any, start: number, end: number, mark: Mark) {
-        FormattedTextBoxComment.textBox = textBox;
-        FormattedTextBoxComment.start = start;
-        FormattedTextBoxComment.end = end;
-        FormattedTextBoxComment.mark = mark;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
-    }
-
-    static update(view: EditorView, lastState?: EditorState) {
-        const state = view.state;
-        // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) {
-            return;
-        }
-        FormattedTextBoxComment.linkDoc = undefined;
-
-        const textBox = FormattedTextBoxComment.textBox;
-        if (!textBox || !textBox.props) {
-            return;
-        }
-        let set = "none";
-        let nbef = 0;
-        FormattedTextBoxComment.tooltipInput.style.display = "none";
-        FormattedTextBoxComment.tooltip.style.width = "";
-        FormattedTextBoxComment.tooltip.style.height = "";
-        (FormattedTextBoxComment.tooltipText as any).href = "";
-        FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
-        FormattedTextBoxComment.tooltipText.style.overflow = "";
-        // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
-        if (state.selection.$from) {
-            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
-            const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
-            const noselection = view.state.selection.$from === view.state.selection.$to;
-            let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
-            const mark = child && findOtherUserMark(child.marks);
-            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
-            }
-            if (mark && child && ((nbef && naft) || !noselection)) {
-                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
-                set = "";
-                FormattedTextBoxComment.tooltipInput.style.display = "";
-            }
-        }
-        // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
-        if (set === "none" && state.selection.$from) {
-            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
-            const naft = findEndOfMark(state.selection.$from, view, findLinkMark);
-            let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
-            const mark = child && findLinkMark(child.marks);
-            if (mark && child && nbef && naft && mark.attrs.showPreview) {
-                FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
-                (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
-                if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
-                    wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
-                } else {
-                    FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
-                    FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
-                }
-                if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    FormattedTextBoxComment.tooltipText.textContent = "target not found...";
-                    (FormattedTextBoxComment.tooltipText as any).href = "";
-                    const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    try {
-                        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-                    } catch (e) { }
-                    docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
-                            (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
-                            FormattedTextBoxComment.linkDoc = linkDoc;
-                            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
-                            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
-                            if (anchor !== target && anchor && target) {
-                                target.scrollY = NumCast(anchor?.y);
-                            }
-                            if (target) {
-                                ReactDOM.render(<ContentFittingDocumentView
-                                    Document={target}
-                                    LibraryPath={emptyPath}
-                                    fitToBox={true}
-                                    moveDocument={returnFalse}
-                                    rootSelected={returnFalse}
-                                    getTransform={Transform.Identity}
-                                    active={returnFalse}
-                                    addDocument={returnFalse}
-                                    removeDocument={returnFalse}
-                                    addDocTab={returnFalse}
-                                    pinToPres={returnFalse}
-                                    dontRegisterView={true}
-                                    renderDepth={1}
-                                    PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
-                                    PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
-                                    focus={emptyFunction}
-                                    whenActiveChanged={returnFalse}
-                                />, FormattedTextBoxComment.tooltipText);
-                                FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
-                                FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
-                            }
-                            // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field.  probably need an 'overviewLayout' and then just display the target Document ....
-                            // let text = ext && StrCast(ext.text);
-                            // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
-                        }
-                    });
-                }
-                set = "";
-            }
-        }
-        if (set !== "none") {
-            // These are in screen coordinates
-            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
-            const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-            // The box in which the tooltip is positioned, to use as base
-            const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
-            // Find a center-ish x position from the selection endpoints (when
-            // crossing lines, end may be more to the left)
-            const left = Math.max((start.left + end.left) / 2, start.left + 3);
-            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
-        }
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
-    }
-
-    destroy() { }
-}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
new file mode 100644
index 000000000..d94fe7fc6
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -0,0 +1,95 @@
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
+import { StepMap } from "prosemirror-transform";
+import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { List } from "../../../../new_fields/List";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+
+import React = require("react");
+
+import { schema } from "./schema_rts";
+
+interface IDashDocCommentView {
+    node: any;
+    view: any;
+    getPos: any;
+}
+
+export class DashDocCommentView extends React.Component<IDashDocCommentView>{
+    constructor(props: IDashDocCommentView) {
+        super(props);
+    }
+
+    targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+        for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) {
+            const m = this.props.view.state.doc.nodeAt(i);
+            if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) {
+                return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+            }
+        }
+        const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" });
+        this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc));
+        setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+        return undefined;
+    }
+
+    onPointerDownCollapse = (e: any) => e.stopPropagation();
+
+    onPointerUpCollapse = (e: any) => {
+        const target = this.targetNode();
+        if (target) {
+            const expand = target.hidden;
+            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+            this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
+            setTimeout(() => {
+                expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+                try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
+            }, 0);
+        }
+        e.stopPropagation();
+    }
+
+    onPointerEnterCollapse = (e: any) => {
+        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    onPointerLeaveCollapse = (e: any) => {
+        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    render() {
+
+        const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid;
+
+        return (
+            <span
+                className="formattedTextBox-inlineComment"
+                id={collapsedId}
+                onPointerDown={this.onPointerDownCollapse}
+                onPointerUp={this.onPointerUpCollapse}
+                onPointerEnter={this.onPointerEnterCollapse}
+                onPointerLeave={this.onPointerLeaveCollapse}
+            >
+
+            </span >
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
new file mode 100644
index 000000000..9fe8fa320
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -0,0 +1,269 @@
+import { IReactionDisposer, reaction } from "mobx";
+import { NodeSelection } from "prosemirror-state";
+import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { Docs } from "../../../documents/Documents";
+import { DocumentView } from "../DocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { Transform } from "../../../util/Transform";
+import React = require("react");
+
+interface IDashDocView {
+    node: any;
+    view: any;
+    getPos: any;
+    tbox?: FormattedTextBox;
+    self: any;
+}
+
+export class DashDocView extends React.Component<IDashDocView> {
+
+    _dashDoc: Doc | undefined;
+    _reactionDisposer: IReactionDisposer | undefined;
+    _renderDisposer: IReactionDisposer | undefined;
+    _textBox: FormattedTextBox;
+    _finalLayout: any;
+    _resolvedDataDoc: any;
+
+
+    //    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+
+    constructor(props: IDashDocView) {
+        super(props);
+
+        const node = this.props.node;
+        this._textBox = this.props.tbox as FormattedTextBox;
+
+        const alias = node.attrs.alias;
+        const docid = node.attrs.docid || this._textBox.props.Document[Id];
+
+        DocServer.GetRefField(docid + alias).then(async dashDoc => {
+            if (!(dashDoc instanceof Doc)) {
+                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
+                    if (dashDocBase instanceof Doc) {
+                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
+                        aliasedDoc.layoutKey = "layout";
+                        node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+                        this._dashDoc = aliasedDoc;
+                        //                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
+                    }
+                });
+            } else {
+                this._dashDoc = dashDoc;
+                //                self.doRender(dashDoc, removeDoc, node, view, getPos);
+            }
+        });
+
+        this.onPointerLeave = this.onPointerLeave.bind(this);
+        this.onPointerEnter = this.onPointerEnter.bind(this);
+        this.onKeyDown = this.onKeyDown.bind(this);
+        this.onKeyPress = this.onKeyPress.bind(this);
+        this.onKeyUp = this.onKeyUp.bind(this);
+        this.onWheel = this.onWheel.bind(this);
+    }
+    /* #region Internal functions */
+
+    removeDoc = () => {
+        const view = this.props.view;
+        const pos = this.props.getPos();
+        const ns = new NodeSelection(view.state.doc.resolve(pos));
+        view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+        return true;
+    }
+
+    getDocTransform = () => {
+        const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement;
+        const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement);
+        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
+    }
+    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
+
+    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
+
+    onKeyPress = (e: any) => {
+        e.stopPropagation();
+    }
+    onWheel = (e: any) => {
+        e.preventDefault();
+    }
+    onKeyUp = (e: any) => {
+        e.stopPropagation();
+    }
+    onKeyDown = (e: any) => {
+        e.stopPropagation();
+        if (e.key === "Tab" || e.key === "Enter") {
+            e.preventDefault();
+        }
+    }
+    onPointerLeave = () => {
+        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
+        if (ele) {
+            (ele as HTMLDivElement).style.backgroundColor = "";
+        }
+    }
+    onPointerEnter = () => {
+        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
+        if (ele) {
+            (ele as HTMLDivElement).style.backgroundColor = "orange";
+        }
+    }
+    /*endregion*/
+
+    componentWillMount = () => {
+        this._reactionDisposer?.();
+    }
+
+    componentDidUpdate = () => {
+
+        this._renderDisposer?.();
+        this._renderDisposer = reaction(() => {
+
+            const dashDoc = this._dashDoc as Doc;
+            const dashLayoutDoc = Doc.Layout(dashDoc);
+            const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey);
+
+            if (finalLayout) {
+                if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
+                    finalLayout.rootDocument = dashDoc.aliasOf;
+                }
+                const layoutKey = StrCast(finalLayout.layoutKey);
+                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
+                if (finalLayout !== dashDoc && finalKey) {
+                    const finalLayoutField = finalLayout[finalKey];
+                    if (finalLayoutField instanceof ObjectField) {
+                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
+                    }
+                }
+                this._finalLayout = finalLayout;
+                this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null);
+                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
+            }
+        },
+            (res) => {
+
+                if (res) {
+                    this._finalLayout = res.finalLayout;
+                    this._resolvedDataDoc = res.resolvedDataDoc;
+
+                    this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc),
+                }
+            },
+            { fireImmediately: true });
+
+    }
+
+    render() {
+        // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
+
+        const node = this.props.node;
+        const view = this.props.view;
+        const getPos = this.props.getPos;
+
+        const spanStyle = {
+            width: this.props.node.props.width,
+            height: this.props.node.props.height,
+            position: 'absolute' as 'absolute',
+            display: 'inline-block'
+        };
+
+
+        const outerStyle = {
+            position: "relative" as "relative",
+            textIndent: "0",
+            border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")),
+            width: this.props.node.props.width,
+            height: this.props.node.props.height,
+            display: this.props.node.props.hidden ? "none" : "inline-block",
+            float: this.props.node.props.float,
+        };
+
+        const dashDoc = this._dashDoc as Doc;
+        const self = this;
+        const dashLayoutDoc = Doc.Layout(dashDoc);
+        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
+        const resolvedDataDoc = this._resolvedDataDoc; //Added this
+
+        if (!finalLayout) {
+            return <div></div>;
+            // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
+        } else {
+
+            this._reactionDisposer?.();
+            this._reactionDisposer = reaction(() =>
+                ({
+                    dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()],
+                    color: finalLayout.color
+                }),
+                ({ 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().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+                }, { fireImmediately: true });
+
+            if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
+                try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                } catch (e) {
+                    console.log(e);
+                }
+            }
+
+
+            //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
+            //    ReactDOM.unmountComponentAtNode(this._dashSpan);
+
+            return (
+                <span id="dash-document-view-outer"
+                    className="outer"
+                    style={outerStyle}
+                >
+                    <div id="dashSpan"
+                        className="dash-span"
+                        style={spanStyle}
+                        onPointerLeave={this.onPointerLeave}
+                        onPointerEnter={this.onPointerEnter}
+                        onKeyDown={this.onKeyDown}
+                        onKeyPress={this.onKeyPress}
+                        onKeyUp={this.onKeyUp}
+                        onWheel={this.onWheel}
+                    >
+                        <DocumentView
+                            Document={finalLayout}
+                            DataDoc={resolvedDataDoc}
+                            LibraryPath={this._textBox.props.LibraryPath}
+                            fitToBox={BoolCast(dashDoc._fitToBox)}
+                            addDocument={returnFalse}
+                            rootSelected={this._textBox.props.isSelected}
+                            removeDocument={this.removeDoc}
+                            ScreenToLocalTransform={this.getDocTransform}
+                            addDocTab={this._textBox.props.addDocTab}
+                            pinToPres={returnFalse}
+                            renderDepth={self._textBox.props.renderDepth + 1}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
+                            PanelWidth={finalLayout[WidthSym]}
+                            PanelHeight={finalLayout[HeightSym]}
+                            focus={this.outerFocus}
+                            backgroundColor={returnEmptyString}
+                            parentActive={returnFalse}
+                            whenActiveChanged={returnFalse}
+                            bringToFront={emptyFunction}
+                            dontRegisterView={false}
+                            ContainingCollectionView={this._textBox.props.ContainingCollectionView}
+                            ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
+                            ContentScaling={this.contentScaling}
+                        />
+
+                    </div>
+                </span>
+            );
+
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
new file mode 100644
index 000000000..35ff9c1e6
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -0,0 +1,36 @@
+.dashFieldView {
+    position: relative;
+    display: inline-block;
+
+    .dashFieldView-enumerables {
+        width: 10px;
+        height: 10px;
+        position: relative;
+        display: inline-block;
+        background: dimGray;
+    }
+    .dashFieldView-fieldCheck {
+        min-width: 12px;
+        position: relative;
+        display: inline-block;
+        background-color: rgba(155, 155, 155, 0.24);
+    }
+    .dashFieldView-labelSpan {
+        position: relative;
+        display: inline-block;
+        font-size: small;
+    }
+    .dashFieldView-fieldSpan {
+        min-width: 20px;
+        margin-left: 2px;
+        margin-right: 5px;
+        position: relative;
+        display: inline-block;
+        background-color: rgba(155, 155, 155, 0.24);
+        span {
+            min-width: 100%;
+            display: inline-block;
+        }
+    }
+}
+        
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
new file mode 100644
index 000000000..82c3185e7
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -0,0 +1,211 @@
+import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
+import { Doc, DocListCast, Field } from "../../../../new_fields/Doc";
+import { List } from "../../../../new_fields/List";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../../new_fields/Types";
+import { DocServer } from "../../../DocServer";
+import { CollectionViewType } from "../../collections/CollectionView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import React = require("react");
+import * as ReactDOM from 'react-dom';
+import "./DashFieldView.scss";
+import { observer } from "mobx-react";
+
+
+export class DashFieldView {
+    _fieldWrapper: HTMLDivElement; // container for label and value
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._fieldWrapper = document.createElement("div");
+        this._fieldWrapper.style.width = node.attrs.width;
+        this._fieldWrapper.style.height = node.attrs.height;
+        this._fieldWrapper.style.fontWeight = "bold";
+        this._fieldWrapper.style.position = "relative";
+        this._fieldWrapper.style.display = "inline-block";
+        this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
+        this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
+        this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+
+        ReactDOM.render(<DashFieldViewInternal
+            fieldKey={node.attrs.fieldKey}
+            docid={node.attrs.docid}
+            width={node.attrs.width}
+            height={node.attrs.height}
+            view={view}
+            getPos={getPos}
+            tbox={tbox}
+        />, this._fieldWrapper);
+        (this as any).dom = this._fieldWrapper;
+    }
+    destroy() {
+        ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+    }
+    selectNode() { }
+
+}
+interface IDashFieldViewInternal {
+    fieldKey: string;
+    docid: string;
+    view: any;
+    getPos: any;
+    tbox: FormattedTextBox;
+    width: number;
+    height: number;
+}
+
+@observer
+export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBoxDoc: Doc;
+    _fieldKey: string;
+    _fieldStringRef = React.createRef<HTMLSpanElement>();
+    @observable _showEnumerables: boolean = false;
+    @observable _dashDoc: Doc | undefined;
+
+    constructor(props: IDashFieldViewInternal) {
+        super(props);
+        this._fieldKey = this.props.fieldKey;
+        this._textBoxDoc = this.props.tbox.props.Document;
+
+        if (this.props.docid) {
+            DocServer.GetRefField(this.props.docid).
+                then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+        } else {
+            this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc;
+        }
+    }
+    componentWillUnmount() {
+        this._reactionDisposer?.();
+    }
+
+    // set the display of the field's value (checkbox for booleans, span of text for strings)
+    @computed get fieldValueContent() {
+        if (this._dashDoc) {
+            const dashVal = this._dashDoc[this._fieldKey];
+            const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
+            const boolVal = Cast(fval, "boolean", null);
+            const strVal = Field.toString(fval as Field) || "";
+
+            // field value is a boolean, so use a checkbox or similar widget to display it
+            if (boolVal === true || boolVal === false) {
+                return <input
+                    className="dashFieldView-fieldCheck"
+                    type="checkbox" checked={boolVal}
+                    onChange={e => this._dashDoc![this._fieldKey] = e.target.checked}
+                />;
+            }
+            else // field value is a string, so display it as an editable span
+            {
+                // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
+                // use React events.  Essentially, React events occur after native events have been processed, so corresponding React events
+                // will never fire because Prosemirror has handled the native events.  So we add listeners for native events here.
+                return <span contentEditable={true} ref={r => {
+                    r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
+                    r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
+                    r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
+                }}>
+                    {strVal}
+                </span>;
+            }
+        }
+    }
+
+    // we need to handle all key events on the input span or else they will propagate to prosemirror.
+    @action
+    fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
+        if (e.key === "Enter") {  // handle the enter key by "submitting" the current text to Dash's database. 
+            e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
+            this.updateText(span.textContent!, true);
+            e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
+        }
+        if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
+            if (window.getSelection) {
+                const range = document.createRange();
+                range.selectNodeContents(span);
+                window.getSelection()!.removeAllRanges();
+                window.getSelection()!.addRange(range);
+            }
+            e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
+        }
+        e.stopPropagation();  // we need to handle all events or else they will propagate to prosemirror.
+    }
+
+    @action
+    updateText = (nodeText: string, forceMatch: boolean) => {
+        this._showEnumerables = false;
+        if (nodeText) {
+            const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
+
+            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
+            // holds the different enumerated values for the field in the titles of its collected documents.
+            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
+            DocServer.GetRefField(this._fieldKey).then(options => {
+                let modText = "";
+                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+                if (modText) {
+                    //  elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
+                    Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
+                    this._dashDoc![this._fieldKey] = modText;
+                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
+                else if (nodeText.startsWith(":=")) {
+                    this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
+                } else if (nodeText.startsWith("=:=")) {
+                    Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
+                } else {
+                    this._dashDoc![this._fieldKey] = newText;
+                }
+            });
+        }
+    }
+
+    // display a collection of all the enumerable values for this field
+    onPointerDownEnumerables = async (e: any) => {
+        e.stopPropagation();
+        const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
+        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
+    }
+
+
+    // clicking on the label creates a pivot view collection of all documents
+    // in the same collection.  The pivot field is the fieldKey of this label
+    onPointerDownLabelSpan = (e: any) => {
+        e.stopPropagation();
+        let container = this.props.tbox.props.ContainingCollectionView;
+        while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
+            container = container.props.ContainingCollectionView;
+        }
+        if (container) {
+            const alias = Doc.MakeAlias(container.props.Document);
+            alias.viewType = CollectionViewType.Time;
+            let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
+            if (!list) {
+                alias.schemaColumns = list = new List<SchemaHeaderField>();
+            }
+            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;
+            this.props.tbox.props.addDocTab(alias, "onRight");
+        }
+    }
+
+    render() {
+        return <div className="dashFieldView" style={{
+            width: this.props.width,
+            height: this.props.height,
+        }}>
+            <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
+                {this._fieldKey}
+            </span>
+
+            <div className="dashFieldView-fieldSpan">
+                {this.fieldValueContent}
+            </div>
+
+            {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
+
+        </div >;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
new file mode 100644
index 000000000..ee21fb765
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -0,0 +1,162 @@
+import { EditorView } from "prosemirror-view";
+import { EditorState } from "prosemirror-state";
+import { keymap } from "prosemirror-keymap";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { schema } from "./schema_rts";
+import { redo, undo } from "prosemirror-history";
+import { StepMap } from "prosemirror-transform";
+
+import React = require("react");
+
+interface IFootnoteView {
+    innerView: any;
+    outerView: any;
+    node: any;
+    dom: any;
+    getPos: any;
+}
+
+export class FootnoteView extends React.Component<IFootnoteView>  {
+    _innerView: any;
+    _node: any;
+
+    constructor(props: IFootnoteView) {
+        super(props);
+        const node = this.props.node;
+        const outerView = this.props.outerView;
+        const _innerView = this.props.innerView;
+        const getPos = this.props.getPos;
+    }
+
+    selectNode() {
+        const attrs = { ...this.props.node.attrs };
+        attrs.visibility = true;
+        this.dom.classList.add("ProseMirror-selectednode");
+        if (!this.props.innerView) this.open();
+    }
+
+    deselectNode() {
+        const attrs = { ...this.props.node.attrs };
+        attrs.visibility = false;
+        this.dom.classList.remove("ProseMirror-selectednode");
+        if (this.props.innerView) this.close();
+    }
+    open() {
+        // Append a tooltip to the outer node
+        const tooltip = this.dom.appendChild(document.createElement("div"));
+        tooltip.className = "footnote-tooltip";
+        // And put a sub-ProseMirror into that
+        this.props.innerView.defineProperty(new EditorView(tooltip, {
+            // You can use any node as an editor document
+            state: EditorState.create({
+                doc: this.props.node,
+                plugins: [keymap(baseKeymap),
+                keymap({
+                    "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch),
+                    "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch),
+                    "Mod-b": toggleMark(schema.marks.strong)
+                }),
+                    // new Plugin({
+                    //     view(newView) {
+                    //         // TODO -- make this work with RichTextMenu
+                    //         // return FormattedTextBox.getToolTip(newView);
+                    //     }
+                    // })
+                ],
+
+            }),
+            // This is the magic part
+            dispatchTransaction: this.dispatchInner.bind(this),
+            handleDOMEvents: {
+                pointerdown: ((view: any, e: PointerEvent) => {
+                    // Kludge to prevent issues due to the fact that the whole
+                    // footnote is node-selected (and thus DOM-selected) when
+                    // the parent editor is focused.
+                    e.stopPropagation();
+                    document.addEventListener("pointerup", this.ignore, true);
+                    if (this.props.outerView.hasFocus()) this.props.innerView.focus();
+                }) as any
+            }
+        }));
+        setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0);
+    }
+
+    ignore = (e: PointerEvent) => {
+        e.stopPropagation();
+        document.removeEventListener("pointerup", this.ignore, true);
+    }
+
+    dispatchInner(tr: any) {
+        const { state, transactions } = this.props.innerView.state.applyTransaction(tr);
+        this.props.innerView.updateState(state);
+
+        if (!tr.getMeta("fromOutside")) {
+            const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1);
+            for (const transaction of transactions) {
+                const steps = transaction.steps;
+                for (const step of steps) {
+                    outerTr.step(step.map(offsetMap));
+                }
+            }
+            if (outerTr.docChanged) this.props.outerView.dispatch(outerTr);
+        }
+    }
+    update(node: any) {
+        if (!node.sameMarkup(this.props.node)) return false;
+        this._node = node; //not sure
+        if (this.props.innerView) {
+            const state = this.props.innerView.state;
+            const start = node.content.findDiffStart(state.doc.content);
+            if (start !== null) {
+                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
+                const overlap = start - Math.min(endA, endB);
+                if (overlap > 0) { endA += overlap; endB += overlap; }
+                this.props.innerView.dispatch(
+                    state.tr
+                        .replace(start, endB, node.slice(start, endA))
+                        .setMeta("fromOutside", true));
+            }
+        }
+        return true;
+    }
+    onPointerUp = (e: any) => {
+        this.toggle(e);
+    }
+
+    toggle = (e: any) => {
+        e.preventDefault();
+        if (this.props.innerView) this.close();
+        else {
+            this.open();
+        }
+    }
+
+    close() {
+        this.props.innerView && this.props.innerView.destroy();
+        this._innerView = null;
+        this.dom.textContent = "";
+    }
+
+    destroy() {
+        if (this.props.innerView) this.close();
+    }
+
+    stopEvent(event: any) {
+        return this.props.innerView && this.props.innerView.dom.contains(event.target);
+    }
+
+    ignoreMutation() { return true; }
+
+
+    render() {
+        return (
+            <div
+                className="footnote"
+                onPointerUp={this.onPointerUp}>
+                <div className="footnote-tooltip" >
+
+                </div >
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
new file mode 100644
index 000000000..477a2ca08
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -0,0 +1,265 @@
+@import "../../globalCssVariables";
+
+.ProseMirror {
+    width: 100%;
+    height: 100%;
+    min-height: 100%;
+}
+
+.ProseMirror:focus {
+    outline: none !important;
+}
+
+.formattedTextBox-cont {
+    touch-action: none;
+    cursor: text;
+    background: inherit;
+    padding: 0;
+    border-width: 0px;
+    border-radius: inherit;
+    border-color: $intermediate-color;
+    box-sizing: border-box;
+    background-color: inherit;
+    border-style: solid;
+    overflow-y: auto;
+    overflow-x: hidden;
+    color: initial;
+    max-height: 100%;
+    display: flex;
+    flex-direction: row;
+    transition: opacity 1s;
+
+    .formattedTextBox-dictation {
+        height: 12px;
+        width: 10px;
+        top: 0px;
+        left: 0px;
+        position: absolute;
+    }
+}
+.formattedTextBox-outer {
+    position: relative;
+    overflow: auto;
+    display: inline-block;
+    width: 100%;
+    height: 100%;
+}
+
+.formattedTextBox-sidebar-handle {
+    position: absolute;
+    top: calc(50% - 17.5px);
+    width: 10px;
+    height: 35px;
+    background: lightgray;
+    border-radius: 20px;
+    cursor:grabbing;
+}
+
+.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
+    right: 0;
+    left: unset;
+}
+
+.formattedTextBox-sidebar,
+.formattedTextBox-sidebar-inking {
+    border-left: dashed 1px black;
+    height: 100%;
+    display: inline-block;
+    position: absolute;
+    right: 0;
+
+    .collectionfreeformview-container {
+        position: relative;
+    }
+    
+    >.formattedTextBox-sidebar-handle {
+        right: unset;
+        left: -5;
+    }
+}
+
+.formattedTextBox-sidebar-inking {
+    pointer-events: all;
+}
+
+.formattedTextBox-inner-rounded {
+    height: 70%;
+    width: 85%;
+    position: absolute;
+    overflow: auto;
+    top: 15%;
+    left: 10%;
+}
+
+.formattedTextBox-inner-rounded,
+.formattedTextBox-inner {
+    height: 100%;
+    white-space: pre-wrap; 
+}
+
+// .menuicon {
+//     display: inline-block;
+//     border-right: 1px solid rgba(0, 0, 0, 0.2);
+//     color: #888;
+//     line-height: 1;
+//     padding: 0 7px;
+//     margin: 1px;
+//     cursor: pointer;
+//     text-align: center;
+//     min-width: 1.4em;
+// }
+
+.strong,
+.heading {
+    font-weight: bold;
+}
+
+.em {
+    font-style: italic;
+}
+
+.userMarkOpen {
+    background: rgba(255, 255, 0, 0.267);
+    display: inline;
+}
+
+.userMark {
+    background: rgba(255, 255, 0, 0.267);
+    font-size: 2px;
+    display: inline-grid;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 10px;
+    min-height: 10px;
+    text-align: center;
+    align-content: center;
+}
+
+footnote {
+    display: inline-block;
+    position: relative;
+    cursor: pointer;
+
+    div {
+        padding: 0 !important;
+    }
+}
+
+footnote::after {
+    content: counter(prosemirror-footnote);
+    vertical-align: super;
+    font-size: 75%;
+    counter-increment: prosemirror-footnote;
+}
+
+.ProseMirror {
+    counter-reset: prosemirror-footnote;
+}
+
+.footnote-tooltip {
+    cursor: auto;
+    font-size: 75%;
+    position: absolute;
+    left: -30px;
+    top: calc(100% + 10px);
+    background: silver;
+    padding: 3px;
+    border-radius: 2px;
+    max-width: 100px;
+    min-width: 50px;
+    width: max-content;
+}
+
+.prosemirror-attribution {
+    font-size: 8px;
+}
+
+.footnote-tooltip::before {
+    border: 5px solid silver;
+    border-top-width: 0px;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    position: absolute;
+    top: -5px;
+    left: 27px;
+    content: " ";
+    height: 0;
+    width: 0;
+}
+
+
+.formattedTextBox-inlineComment {
+    position: relative;
+    width: 40px;
+    height: 20px;
+    &::before {
+        content: "→";
+    }
+    &:hover {
+        background: orange;
+    }
+}
+
+.formattedTextBox-summarizer {
+    opacity: 0.5;
+    position: relative;
+    width: 40px;
+    height: 20px;
+    &::after {
+        content: "←";
+    }
+}
+
+.formattedTextBox-summarizer-collapsed {
+    opacity: 0.5;
+    position: relative;
+    width: 40px;
+    height: 20px;
+    &::after {
+        content: "...";
+    }
+}
+
+.ProseMirror {
+    touch-action: none;
+    span {
+        font-family: inherit;
+    }
+
+    ol, ul {
+        counter-reset: deci1 0 multi1 0;
+        padding-left: 1em;
+        font-family: inherit;
+    }
+    ol {
+        margin-left: 1em;
+        font-family: inherit;
+    }
+
+    .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
+    .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
+    .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+    .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
+    .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
+    .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
+    .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
+
+    .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
+    .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
+    .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+    .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
+
+    .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
+    .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
+    .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
+    .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
+    .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
+    .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
+    .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
+    
+    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
+    .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
+    .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
+    .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
new file mode 100644
index 000000000..248b4f467
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -0,0 +1,1303 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { isEqual } from "lodash";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { baseKeymap } from "prosemirror-commands";
+import { history } from "prosemirror-history";
+import { inputRules } from 'prosemirror-inputrules';
+import { keymap } from "prosemirror-keymap";
+import { Fragment, Mark, Node, Slice } from "prosemirror-model";
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
+import { ReplaceStep } from 'prosemirror-transform';
+import { EditorView } from "prosemirror-view";
+import { DateField } from '../../../../new_fields/DateField';
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { Id } from '../../../../new_fields/FieldSymbols';
+import { InkTool } from '../../../../new_fields/InkField';
+import { PrefetchProxy } from '../../../../new_fields/Proxy';
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { RichTextUtils } from '../../../../new_fields/RichTextUtils';
+import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { TraceMobx } from '../../../../new_fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../../Utils';
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
+import { DocServer } from "../../../DocServer";
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { DictationManager } from '../../../util/DictationManager';
+import { DragManager } from "../../../util/DragManager";
+import { makeTemplate } from '../../../util/DropConverter';
+import buildKeymap from "./ProsemirrorExampleTransfer";
+import RichTextMenu from './RichTextMenu';
+import { RichTextRules } from "./RichTextRules";
+import { DashDocCommentView, DashDocView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "./RichTextSchema";
+// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "./RichTextSchema";
+// import { OrderedListView } from "./RichTextSchema";
+// import { ImageResizeView } from "./ImageResizeView";
+// import { DashDocCommentView } from "./DashDocCommentView";
+// import { FootnoteView } from "./FootnoteView";
+// import { SummaryView } from "./SummaryView";
+// import { DashDocView } from "./DashDocView";
+import { DashFieldView } from "./DashFieldView";
+
+import { schema } from "./schema_rts";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
+import { ContextMenu } from '../../ContextMenu';
+import { ContextMenuProps } from '../../ContextMenuItem';
+import { ViewBoxAnnotatableComponent } from "../../DocComponent";
+import { DocumentButtonBar } from '../../DocumentButtonBar';
+import { InkingControl } from "../../InkingControl";
+import { AudioBox } from '../AudioBox';
+import { FieldView, FieldViewProps } from "../FieldView";
+import "./FormattedTextBox.scss";
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
+import React = require("react");
+
+library.add(faEdit);
+library.add(faSmile, faTextHeight, faUpload);
+
+export interface FormattedTextBoxProps {
+    hideOnLeave?: boolean;
+    makeLink?: () => Opt<Doc>;
+    xMargin?: number;
+    yMargin?: number;
+}
+
+const richTextSchema = createSchema({
+    documentText: "string"
+});
+
+export const GoogleRef = "googleDocId";
+
+type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
+const RichTextDocument = makeInterface(richTextSchema, documentSchema);
+
+type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
+
+@observer
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
+    public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+    public static Instance: FormattedTextBox;
+    public ProseRef?: HTMLDivElement;
+    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+    private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _editorView: Opt<EditorView>;
+    private _applyingChange: boolean = false;
+    private _searchIndex = 0;
+    private _sidebarMovement = 0;
+    private _lastX = 0;
+    private _lastY = 0;
+    private _undoTyping?: UndoManager.Batch;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
+    private dropDisposer?: DragManager.DragDropDisposer;
+
+    @computed get _recording() { return this.dataDoc.audioState === "recording"; }
+    set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
+
+    @observable private _entered = false;
+
+    public static FocusedBox: FormattedTextBox | undefined;
+    public static SelectOnLoad = "";
+    public static SelectOnLoadChar = "";
+    public static IsFragment(html: string) {
+        return html.indexOf("data-pm-slice") !== -1;
+    }
+    public static GetHref(html: string): string {
+        const parser = new DOMParser();
+        const parsedHtml = parser.parseFromString(html, 'text/html');
+        if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
+            (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
+            return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
+        }
+        return "";
+    }
+    public static GetDocFromUrl(url: string) {
+        if (url.startsWith(document.location.origin)) {
+            const split = new URL(url).pathname.split("doc/");
+            const docid = split[split.length - 1];
+            return docid;
+        }
+        return "";
+    }
+
+    @undoBatch
+    public setFontColor(color: string) {
+        const view = this._editorView!;
+        if (view.state.selection.from === view.state.selection.to) return false;
+        if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
+            this.layoutDoc.color = color;
+        }
+        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+        view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
+        return true;
+    }
+
+    constructor(props: any) {
+        super(props);
+        FormattedTextBox.Instance = this;
+        this.updateHighlights();
+    }
+
+    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
+    linkOnDeselect: Map<string, string> = new Map();
+
+    doLinkOnDeselect() {
+        Array.from(this.linkOnDeselect.entries()).map(entry => {
+            const key = entry[0];
+            const value = entry[1];
+            const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+            DocServer.GetRefField(value).then(doc => {
+                DocServer.GetRefField(id).then(linkDoc => {
+                    this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
+                    DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
+                    if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
+                    else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+                });
+            });
+        });
+        this.linkOnDeselect.clear();
+    }
+
+    dispatchTransaction = (tx: Transaction) => {
+        if (this._editorView) {
+            const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
+            if (metadata) {
+                const range = tx.selection.$from.blockRange(tx.selection.$to);
+                let text = range ? tx.doc.textBetween(range.start, range.end) : "";
+                let textEndSelection = tx.selection.to;
+                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { }
+                text = text.substr(0, textEndSelection - range!.start);
+                text = text.split(" ")[text.split(" ").length - 1];
+                const split = text.split("::");
+                if (split.length > 1 && split[1]) {
+                    const key = split[0];
+                    const value = split[split.length - 1];
+                    this.linkOnDeselect.set(key, value);
+
+                    const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+                    const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", 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);
+                    this.dataDoc[key] = value;
+                }
+            }
+            const state = this._editorView.state.apply(tx);
+            this._editorView.updateState(state);
+            (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
+
+            const tsel = this._editorView.state.selection.$from;
+            tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
+            const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
+            const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
+            if (!this._applyingChange) {
+                this._applyingChange = true;
+                this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+                if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+                    this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText);
+                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
+                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+                }
+                this._applyingChange = false;
+            }
+            this.updateTitle();
+            this.tryUpdateHeight();
+        }
+    }
+
+    updateTitle = () => {
+        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
+            const str = this._editorView.state.doc.textContent;
+            const titlestr = str.substr(0, Math.min(40, str.length));
+            this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+        }
+    }
+
+    // needs a better API for taking in a set of words with target documents instead of just one target
+    public hyperlinkTerms = (terms: string[], target: Doc) => {
+        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
+            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+            const tr = this._editorView.state.tr;
+            const flattened: TextSelection[] = [];
+            res.map(r => r.map(h => flattened.push(h)));
+            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
+            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+            const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!;
+            const link = this._editorView.state.schema.marks.link.create({
+                href: Utils.prepend("/doc/" + alink[Id]),
+                title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
+            });
+            this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
+        }
+    }
+    public highlightSearchTerms = (terms: string[]) => {
+        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
+            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
+            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+            let tr = this._editorView.state.tr;
+            const flattened: TextSelection[] = [];
+            res.map(r => r.map(h => flattened.push(h)));
+            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
+            flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
+            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+            this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+        }
+    }
+
+    public unhighlightSearchTerms = () => {
+        if (this._editorView && (this._editorView as any).docView) {
+            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
+            const end = this._editorView.state.doc.nodeSize - 2;
+            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+        }
+    }
+    adoptAnnotation = (start: number, end: number, mark: Mark) => {
+        const view = this._editorView!;
+        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
+        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
+    }
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this.ProseRef = ele;
+        this.dropDisposer?.();
+        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+    }
+
+    @undoBatch
+    @action
+    drop = async (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData) {
+            const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
+            // replace text contents whend dragging with Alt
+            if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
+                if (draggedDoc.data instanceof RichTextField) {
+                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
+                    e.stopPropagation();
+                }
+                // embed document when dragging with a userDropAction or an embedDoc flag set
+            } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
+                const target = de.complete.docDragData.droppedDocuments[0];
+                // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
+                // if (link) {
+                target._fitToBox = true;
+                const node = schema.nodes.dashDoc.create({
+                    width: target[WidthSym](), height: target[HeightSym](),
+                    title: "dashDoc", docid: target[Id],
+                    float: "right"
+                });
+                const view = this._editorView!;
+                view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
+                this.tryUpdateHeight();
+                e.stopPropagation();
+                // }
+            } // otherwise, fall through to outer collection to handle drop
+        } else if (de.complete.linkDragData) {
+            de.complete.linkDragData.linkDropCallback = this.linkDrop;
+        }
+    }
+    linkDrop = (data: DragManager.LinkDragData) => {
+        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);
+    }
+
+    getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
+        let offset = 0;
+
+        if (context === node) return { from: offset, to: offset + node.nodeSize };
+
+        if (node.isBlock) {
+            // tslint:disable-next-line: prefer-for-of
+            for (let i = 0; i < (context.content as any).content.length; i++) {
+                const result = this.getNodeEndpoints((context.content as any).content[i], node);
+                if (result) {
+                    return {
+                        from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
+                        to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
+                    };
+                }
+                offset += (context.content as any).content[i].nodeSize;
+            }
+            return null;
+        } else {
+            return null;
+        }
+    }
+
+
+    //Recursively finds matches within a given node
+    findInNode(pm: EditorView, node: Node, find: string) {
+        let ret: TextSelection[] = [];
+
+        if (node.isTextblock) {
+            let index = 0, foundAt;
+            const ep = this.getNodeEndpoints(pm.state.doc, node);
+            while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
+                const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
+                ret.push(sel);
+                index = index + foundAt + find.length;
+            }
+        } else {
+            node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
+        }
+        return ret;
+    }
+    static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+
+    updateHighlights = () => {
+        clearStyleSheetRules(FormattedTextBox._userStyleSheet);
+        if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
+        }
+        if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
+        }
+        if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            const min = Math.round(Date.now() / 1000 / 60);
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            setTimeout(() => this.updateHighlights());
+        }
+        if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            const hr = Math.round(Date.now() / 1000 / 60 / 60);
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+        }
+    }
+
+    sidebarDown = (e: React.PointerEvent) => {
+        this._lastX = e.clientX;
+        this._lastY = e.clientY;
+        this._sidebarMovement = 0;
+        document.addEventListener("pointermove", this.sidebarMove);
+        document.addEventListener("pointerup", this.sidebarUp);
+        e.stopPropagation();
+        e.preventDefault(); // prevents text from being selected during drag
+    }
+    sidebarMove = (e: PointerEvent) => {
+        const bounds = this.CurrentDiv.getBoundingClientRect();
+        this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
+        this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
+    }
+    sidebarUp = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.sidebarMove);
+        document.removeEventListener("pointerup", this.sidebarUp);
+    }
+
+    toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
+
+    public static get DefaultLayout(): Doc | string | undefined {
+        return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
+    }
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+
+        const funcs: ContextMenuProps[] = [];
+        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
+        funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        !this.props.Document.rootDocument && funcs.push({
+            description: "Make Template", event: () => {
+                this.props.Document.isTemplateDoc = makeTemplate(this.props.Document);
+                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
+            }, icon: "eye"
+        });
+        funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const highlighting: ContextMenuProps[] = [];
+        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+            highlighting.push({
+                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+                    e.stopPropagation();
+                    if (FormattedTextBox._highlights.indexOf(option) === -1) {
+                        FormattedTextBox._highlights.push(option);
+                    } else {
+                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+                    }
+                    this.updateHighlights();
+                }, icon: "expand-arrows-alt"
+            }));
+        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
+
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+        const change = cm.findByDescription("Change Perspective...");
+        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
+
+        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+        DocListCast(noteTypesDoc?.data).forEach(note => {
+            changeItems.push({
+                description: StrCast(note.title), event: undoBatch(() => {
+                    Doc.setNativeView(this.props.Document);
+                    Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }), icon: "eye"
+            });
+        });
+        changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
+        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+
+        const open = cm.findByDescription("Add a Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+
+        openItems.push({
+            description: "FreeForm", event: undoBatch(() => {
+                const alias = Doc.MakeAlias(this.rootDoc);
+                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+                this.props.addDocTab(alias, "onRight");
+            }), icon: "eye"
+        });
+        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
+
+    }
+
+    recordDictation = () => {
+        DictationManager.Controls.listen({
+            interimHandler: this.setCurrentBulletContent,
+            continuous: { indefinite: false },
+        }).then(results => {
+            if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                DictationManager.Controls.stop();
+            }
+            //this._editorView!.focus();
+        });
+    }
+    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
+
+    @action
+    toggleMenubar = () => {
+        this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
+    }
+
+    recordBullet = async () => {
+        const completedCue = "end session";
+        const results = await DictationManager.Controls.listen({
+            interimHandler: this.setCurrentBulletContent,
+            continuous: { indefinite: false },
+            terminators: [completedCue, "bullet", "next"]
+        });
+        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+            DictationManager.Controls.stop();
+            return;
+        }
+        this.nextBullet(this._editorView!.state.selection.to);
+        setTimeout(this.recordBullet, 2000);
+    }
+
+    setCurrentBulletContent = (value: string) => {
+        if (this._editorView) {
+            const state = this._editorView.state;
+            const now = Date.now();
+            let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
+            if (!this._break && state.selection.to !== state.selection.from) {
+                for (let i = state.selection.from; i <= state.selection.to; i++) {
+                    const pos = state.doc.resolve(i);
+                    const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
+                    if (um) {
+                        mark = um;
+                        break;
+                    }
+                }
+            }
+            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
+            this._break = false;
+            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
+            const from = state.selection.from;
+            const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
+            this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
+        }
+    }
+
+    nextBullet = (pos: number) => {
+        if (this._editorView) {
+            const frag = Fragment.fromArray(this.newListItems(2));
+            if (this._editorView.state.doc.resolve(pos).depth >= 2) {
+                const slice = new Slice(frag, 2, 2);
+                let state = this._editorView.state;
+                this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+                pos += 4;
+                state = this._editorView.state;
+                this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+            }
+        }
+    }
+
+    private newListItems = (count: number) => {
+        return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
+    }
+
+    _keymap: any = undefined;
+    _rules: RichTextRules | undefined;
+    @computed get config() {
+        this._keymap = buildKeymap(schema, this.props);
+        this._rules = new RichTextRules(this.props.Document, this);
+        return {
+            schema,
+            plugins: [
+                inputRules(this._rules.inpRules),
+                this.richTextMenuPlugin(),
+                history(),
+                keymap(this._keymap),
+                keymap(baseKeymap),
+                new Plugin({
+                    props: {
+                        attributes: { class: "ProseMirror-example-setup-style" }
+                    }
+                }),
+                formattedTextBoxCommentPlugin
+            ]
+        };
+    }
+
+    makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) {
+        if (this._editorView) {
+            const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
+            this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link).
+                addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link));
+        }
+    }
+    componentDidMount() {
+        this._disposers.buttonBar = reaction(
+            () => DocumentButtonBar.Instance,
+            instance => {
+                if (instance) {
+                    this.pullFromGoogleDoc(this.checkState);
+                    this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true);
+                }
+            }
+        );
+        this._disposers.linkMaker = reaction(
+            () => this.props.makeLink?.(),
+            (linkDoc: Opt<Doc>) => {
+                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);
+                }
+            },
+            { fireImmediately: true }
+        );
+        this._disposers.editorState = reaction(
+            () => {
+                if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
+                    return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
+                }
+                return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
+            },
+            incomingValue => {
+                if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
+                    const updatedState = JSON.parse(incomingValue);
+                    this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+                    this.tryUpdateHeight();
+                }
+            }
+        );
+        this._disposers.pullDoc = reaction(
+            () => this.props.Document[Pulls],
+            () => {
+                if (!DocumentButtonBar.hasPulledHack) {
+                    DocumentButtonBar.hasPulledHack = true;
+                    const unchanged = this.dataDoc.unchanged;
+                    this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
+                }
+            }
+        );
+        this._disposers.pushDoc = reaction(
+            () => this.props.Document[Pushes],
+            () => {
+                if (!DocumentButtonBar.hasPushedHack) {
+                    DocumentButtonBar.hasPushedHack = true;
+                    this.pushToGoogleDoc();
+                }
+            }
+        );
+        this._disposers.height = reaction(
+            () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
+            () => this.tryUpdateHeight()
+        );
+
+        this.setupEditor(this.config, this.props.fieldKey);
+
+        this._disposers.search = reaction(() => this.rootDoc.searchMatch,
+            search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
+            { fireImmediately: true });
+
+        this._disposers.record = reaction(() => this._recording,
+            () => {
+                if (this._recording) {
+                    setTimeout(action(() => {
+                        this.stopDictation(true);
+                        setTimeout(() => this.recordDictation(), 500);
+                    }), 500);
+                } else setTimeout(() => this.stopDictation(true), 0);
+            }
+        );
+        this._disposers.scrollToRegion = reaction(
+            () => StrCast(this.layoutDoc.scrollToLinkID),
+            async (scrollToLinkID) => {
+                const findLinkFrag = (frag: Fragment, editor: EditorView) => {
+                    const nodes: Node[] = [];
+                    frag.forEach((node, index) => {
+                        const examinedNode = findLinkNode(node, editor);
+                        if (examinedNode && examinedNode.textContent) {
+                            nodes.push(examinedNode);
+                            start += index;
+                        }
+                    });
+                    return { frag: Fragment.fromArray(nodes), start: start };
+                };
+                const findLinkNode = (node: Node, editor: EditorView) => {
+                    if (!node.isText) {
+                        const content = findLinkFrag(node.content, editor);
+                        return node.copy(content.frag);
+                    }
+                    const marks = [...node.marks];
+                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
+                    return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+                };
+
+                let start = -1;
+                if (this._editorView && scrollToLinkID) {
+                    const editor = this._editorView;
+                    const ret = findLinkFrag(editor.state.doc.content, editor);
+
+                    if (ret.frag.size > 2 && ret.start >= 0) {
+                        let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
+                        if (ret.frag.firstChild) {
+                            selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+                        }
+                        editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+                        const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+                        setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
+                        setTimeout(() => this.unhighlightSearchTerms(), 2000);
+                    }
+                    Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
+                }
+
+            },
+            { fireImmediately: true }
+        );
+        this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos),
+            pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
+        );
+
+        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
+    }
+
+    pushToGoogleDoc = async () => {
+        this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
+            const modes = GoogleApiClientUtils.Docs.WriteMode;
+            let mode = modes.Replace;
+            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+            if (!reference) {
+                mode = modes.Insert;
+                reference = { title: StrCast(this.dataDoc.title) };
+            }
+            const redo = async () => {
+                if (this._editorView && reference) {
+                    const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
+                    const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                    response && (this.dataDoc[GoogleRef] = response.documentId);
+                    const pushSuccess = response !== undefined && !("errors" in response);
+                    dataDoc.unchanged = pushSuccess;
+                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
+                }
+            };
+            const undo = () => {
+                if (!exportState) {
+                    return;
+                }
+                const content: GoogleApiClientUtils.Docs.Content = {
+                    text: exportState.text,
+                    requests: []
+                };
+                if (reference && content) {
+                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                }
+            };
+            UndoManager.AddEvent({ undo, redo });
+            redo();
+        });
+    }
+
+    pullFromGoogleDoc = async (handler: PullHandler) => {
+        const dataDoc = this.dataDoc;
+        const documentId = StrCast(dataDoc[GoogleRef]);
+        let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
+        if (documentId) {
+            exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
+        }
+        UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
+    }
+
+    updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
+        let pullSuccess = false;
+        if (exportState !== undefined) {
+            pullSuccess = true;
+            dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
+            setTimeout(() => {
+                if (this._editorView) {
+                    const state = this._editorView.state;
+                    const end = state.doc.content.size - 1;
+                    this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
+                }
+            }, 0);
+            dataDoc.title = exportState.title;
+            this.rootDoc.customTitle = true;
+            dataDoc.unchanged = true;
+        } else {
+            delete dataDoc[GoogleRef];
+        }
+        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
+    }
+
+    checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
+        if (exportState && this._editorView) {
+            const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
+            const equalTitles = dataDoc.title === exportState.title;
+            const unchanged = equalContent && equalTitles;
+            dataDoc.unchanged = unchanged;
+            DocumentButtonBar.Instance.setPullState(unchanged);
+        }
+    }
+
+    clipboardTextSerializer = (slice: Slice): string => {
+        let text = "", separated = true;
+        const from = 0, to = slice.content.size;
+        slice.content.nodesBetween(from, to, (node, pos) => {
+            if (node.isText) {
+                text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
+                separated = false;
+            } else if (!separated && node.isBlock) {
+                text += "\n";
+                separated = true;
+            } else if (node.type.name === "hard_break") {
+                text += "\n";
+            }
+        }, 0);
+        return text;
+    }
+
+    sliceSingleNode(slice: Slice) {
+        return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null;
+    }
+
+    handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
+        const cbe = event as ClipboardEvent;
+        const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
+        const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
+        if (pdfDocId && pdfRegionId) {
+            DocServer.GetRefField(pdfDocId).then(pdfDoc => {
+                DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
+                    if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+                        setTimeout(async () => {
+                            const targetField = Doc.LayoutFieldKey(pdfDoc);
+                            const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
+                            targetAnnotations?.push(pdfRegion);
+                        });
+
+                        const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted");
+                        if (link) {
+                            cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
+                            const linkId = link[Id];
+                            const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+                            slice = new Slice(frag, slice.openStart, slice.openEnd);
+                            const tr = view.state.tr.replaceSelection(slice);
+                            view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+                        }
+                    }
+                });
+            });
+            return true;
+        }
+        return false;
+
+
+        function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
+            const nodes: Node[] = [];
+            frag.forEach(node => nodes.push(marker(node)));
+            return Fragment.fromArray(nodes);
+        }
+        function addLinkMark(node: Node, title: string, linkId: string) {
+            if (!node.isText) {
+                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
+                return node.copy(content);
+            }
+            const marks = [...node.marks];
+            const linkIndex = marks.findIndex(mark => mark.type.name === "link");
+            const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
+            marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
+            return node.mark(marks);
+        }
+    }
+
+    private setupEditor(config: any, fieldKey: string) {
+        const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
+        const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"];
+        const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
+        if (this.ProseRef) {
+            const self = this;
+            this._editorView?.destroy();
+            this._editorView = new EditorView(this.ProseRef, {
+                state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
+                handleScrollToSelection: (editorView) => {
+                    const docPos = editorView.coordsAtPos(editorView.state.selection.from);
+                    const viewRect = self._ref.current!.getBoundingClientRect();
+                    if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
+                        docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
+                    }
+                    return true;
+                },
+                dispatchTransaction: this.dispatchTransaction,
+                nodeViews: {
+                    dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
+                    dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
+                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
+                    // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); },
+
+                    // image(node, view, getPos) {
+                    //     //const addDocTab = this.props.addDocTab;
+                    //     return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab });
+                    // },
+                    // // WAS : 
+                    // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); },
+
+                    summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
+                    ordered_list(node, view, getPos) { return new OrderedListView(); },
+                    footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
+                },
+                clipboardTextSerializer: this.clipboardTextSerializer,
+                handlePaste: this.handlePaste,
+            });
+            const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
+            if (startupText) {
+                const { state: { tr }, dispatch } = this._editorView;
+                dispatch(tr.insertText(startupText));
+            }
+        }
+
+        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
+        if (selectOnLoad && !this.props.dontRegisterView) {
+            FormattedTextBox.SelectOnLoad = "";
+            this.props.select(false);
+            FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
+            FormattedTextBox.SelectOnLoadChar = "";
+
+        }
+        (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
+        // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
+        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+    }
+    getFont(font: string) {
+        switch (font) {
+            case "Arial": return schema.marks.arial.create();
+            case "Times New Roman": return schema.marks.timesNewRoman.create();
+            case "Georgia": return schema.marks.georgia.create();
+            case "Comic Sans MS": return schema.marks.comicSans.create();
+            case "Tahoma": return schema.marks.tahoma.create();
+            case "Impact": return schema.marks.impact.create();
+            case "ACrimson Textrial": return schema.marks.crimson.create();
+        }
+        return schema.marks.arial.create();
+    }
+
+    componentWillUnmount() {
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+        this._editorView?.destroy();
+    }
+
+    static _downEvent: any;
+    _downX = 0;
+    _downY = 0;
+    _break = false;
+    onPointerDown = (e: React.PointerEvent): void => {
+        if (this._recording && !e.ctrlKey && e.button === 0) {
+            this.stopDictation(true);
+            this._break = true;
+            const state = this._editorView!.state;
+            const to = state.selection.to;
+            const updated = TextSelection.create(state.doc, to, to);
+            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
+            e.preventDefault();
+            e.stopPropagation();
+            if (this._recording) setTimeout(() => this.recordDictation(), 500);
+        }
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        this.doLinkOnDeselect();
+        FormattedTextBox._downEvent = true;
+        FormattedTextBoxComment.textBox = this;
+        if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
+            e.preventDefault();
+        }
+        if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
+                e.stopPropagation();
+            }
+        }
+        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+            e.preventDefault();
+        }
+    }
+
+    onPointerUp = (e: React.PointerEvent): void => {
+        if (!FormattedTextBox._downEvent) return;
+        FormattedTextBox._downEvent = false;
+        if (!(e.nativeEvent as any).formattedHandled) {
+            FormattedTextBoxComment.textBox = this;
+            FormattedTextBoxComment.update(this._editorView!);
+        }
+        (e.nativeEvent as any).formattedHandled = true;
+
+        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+            e.stopPropagation();
+        }
+        this._downX = this._downY = Number.NaN;
+    }
+
+    @action
+    onFocused = (e: React.FocusEvent): void => {
+        FormattedTextBox.FocusedBox = this;
+        this.tryUpdateHeight();
+
+        // see if we need to preserve the insertion point
+        const prosediv = this.ProseRef?.children?.[0] as any;
+        const keeplocation = prosediv?.keeplocation;
+        prosediv && (prosediv.keeplocation = undefined);
+        const pos = this._editorView?.state.selection.$from.pos || 1;
+        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+        const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
+
+        // jump rich text menu to this textbox
+        const bounds = this._ref.current?.getBoundingClientRect();
+        if (bounds && this.props.Document._chromeStatus !== "disabled") {
+            const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
+            let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
+            if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
+                y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
+            }
+            RichTextMenu.Instance.jumpTo(x, y);
+        }
+    }
+    onPointerWheel = (e: React.WheelEvent): void => {
+        // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
+        if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+            e.stopPropagation();
+        }
+    }
+
+    static _bulletStyleSheet: any = addStyleSheet();
+    static _userStyleSheet: any = addStyleSheet();
+
+    onClick = (e: React.MouseEvent): void => {
+        if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
+            const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+            const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
+                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
+                e.preventDefault();
+            }
+            if (!node && this.ProseRef) {
+                const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+                if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
+                }
+            }
+        }
+        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
+        (e.nativeEvent as any).formattedHandled = true;
+        // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
+        //     let href = (e.target as any).href;
+        //     let location: string;
+        //     if ((e.target as any).attributes.location) {
+        //         location = (e.target as any).attributes.location.value;
+        //     }
+        //     let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+        //     let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
+        //     if (node) {
+        //         let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
+        //         if (link && !(link.attrs.docref && link.attrs.title)) {  // bcz: getting hacky.  this indicates that we clicked on a PDF excerpt quotation.  In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
+        //             href = link && link.attrs.href;
+        //             location = link && link.attrs.location;
+        //         }
+        //     }
+        //     if (href) {
+        //         if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+        //             let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+        //             if (linkClicked) {
+        //                 DocServer.GetRefField(linkClicked).then(async linkDoc => {
+        //                     (linkDoc instanceof Doc) &&
+        //                         DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false);
+        //                 });
+        //             }
+        //         } else {
+        //             let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
+        //             this.props.addDocument && this.props.addDocument(webDoc);
+        //         }
+        //         e.stopPropagation();
+        //         e.preventDefault();
+        //     }
+        // }
+
+        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
+            this.props.select(e.ctrlKey);
+            this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
+        }
+    }
+
+    // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
+    hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
+        clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
+        const pos = this._editorView!.posAtCoords({ left: x, top: y });
+        if (pos && this.props.isSelected(true)) {
+            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
+            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
+            const $pos = this._editorView!.state.doc.resolve(pos.pos);
+            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
+            if ($pos.node().type === schema.nodes.ordered_list) {
+                for (let off = 1; off < 100; off++) {
+                    const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
+                    const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
+                    if (node?.type === schema.nodes.list_item) {
+                        list_node = node;
+                        break;
+                    }
+                }
+            }
+            if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
+                if (select) {
+                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+                    if (!highlightOnly) {
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
+                    }
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                } else if (Math.abs(pos.pos - pos.inside) < 2) {
+                    if (!highlightOnly) {
+                        const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
+                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
+                    }
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                }
+            }
+        }
+    }
+    onMouseUp = (e: React.MouseEvent): void => {
+        e.stopPropagation();
+
+        const view = this._editorView as any;
+        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there 
+        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
+        if (view.mouseDown) {
+            const originalUpHandler = view.mouseDown.up;
+            view.root.removeEventListener("mouseup", originalUpHandler);
+            view.mouseDown.up = (e: MouseEvent) => {
+                !(e as any).formattedHandled && originalUpHandler(e);
+                // e.stopPropagation();
+                (e as any).formattedHandled = true;
+            };
+            view.root.addEventListener("mouseup", view.mouseDown.up);
+        }
+    }
+
+    richTextMenuPlugin() {
+        return new Plugin({
+            view(newView) {
+                RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
+                return RichTextMenu.Instance;
+            }
+        });
+    }
+
+    public static HadSelection: boolean = false;
+    onBlur = (e: any) => {
+        FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
+        //DictationManager.Controls.stop(false);
+        if (this._undoTyping) {
+            this._undoTyping.end();
+            this._undoTyping = undefined;
+        }
+        this.doLinkOnDeselect();
+
+        // move the richtextmenu offscreen
+        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
+    }
+
+    _lastTimedMark: Mark | undefined = undefined;
+    onKeyPress = (e: React.KeyboardEvent) => {
+        if (e.altKey) {
+            e.preventDefault();
+            return;
+        }
+        const state = this._editorView!.state;
+        if (!state.selection.empty && e.key === "%") {
+            this._rules!.EnteringStyle = true;
+            e.preventDefault();
+            e.stopPropagation();
+            return;
+        }
+
+        if (state.selection.empty || !this._rules!.EnteringStyle) {
+            this._rules!.EnteringStyle = false;
+        }
+        if (e.key === "Escape") {
+            this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+            (document.activeElement as any).blur?.();
+            SelectionManager.DeselectAll();
+        }
+        e.stopPropagation();
+        if (e.key === "Tab" || e.key === "Enter") {
+            e.preventDefault();
+        }
+        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+        this._lastTimedMark = mark;
+        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+
+        if (!this._undoTyping) {
+            this._undoTyping = UndoManager.StartBatch("undoTyping");
+        }
+    }
+
+    onscrolled = (ev: React.UIEvent) => {
+        this.props.Document.scrollPos = this._scrollRef.current!.scrollTop;
+    }
+    @action
+    tryUpdateHeight(limitHeight?: number) {
+        let scrollHeight = this._ref.current?.scrollHeight;
+        if (this.layoutDoc._autoHeight && scrollHeight &&
+            getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+            if (limitHeight && scrollHeight > limitHeight) {
+                scrollHeight = limitHeight;
+                this.layoutDoc.limitHeight = undefined;
+                this.layoutDoc._autoHeight = false;
+            }
+            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
+            const dh = NumCast(this.layoutDoc._height, 0);
+            const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+            if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
+                this.layoutDoc._height = newHeight;
+                this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
+            }
+        }
+    }
+
+    @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
+    sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
+    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
+    @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
+    render() {
+        TraceMobx();
+        const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
+        const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
+        if (this.props.isSelected()) {
+            this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
+        } else if (FormattedTextBoxComment.textBox === this) {
+            FormattedTextBoxComment.Hide();
+        }
+        return (
+
+            <div className={`formattedTextBox-cont`} ref={this._ref}
+                style={{
+                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
+                    background: StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
+                    opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
+                    color: StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
+                    pointerEvents: interactive ? "none" : undefined,
+                    fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+                    fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
+                }}
+                onContextMenu={this.specificContextMenu}
+                onKeyDown={this.onKeyPress}
+                onFocus={this.onFocused}
+                onClick={this.onClick}
+                onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
+                onBlur={this.onBlur}
+                onPointerUp={this.onPointerUp}
+                onPointerDown={this.onPointerDown}
+                onMouseUp={this.onMouseUp}
+                onWheel={this.onPointerWheel}
+                onPointerEnter={action(() => this._entered = true)}
+                onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
+                    this._entered = false;
+                    const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
+                    for (let child: any = target; child; child = child?.parentElement) {
+                        if (child === this._ref.current!) {
+                            this._entered = true;
+                        }
+                    }
+                })}
+            >
+                <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
+                    <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+                        style={{
+                            padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
+                            pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                        }} />
+                </div>
+                {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
+                    <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
+                    <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
+                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                        <CollectionFreeFormView {...this.props}
+                            PanelHeight={this.props.PanelHeight}
+                            PanelWidth={this.sidebarWidth}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
+                            annotationsKey={this.annotationKey}
+                            isAnnotationOverlay={false}
+                            focus={this.props.focus}
+                            isSelected={this.props.isSelected}
+                            select={emptyFunction}
+                            active={this.annotationsActive}
+                            ContentScaling={returnOne}
+                            whenActiveChanged={this.whenActiveChanged}
+                            removeDocument={this.removeDocument}
+                            moveDocument={this.moveDocument}
+                            addDocument={this.addDocument}
+                            CollectionView={undefined}
+                            ScreenToLocalTransform={this.sidebarScreenToLocal}
+                            renderDepth={this.props.renderDepth + 1}
+                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+                        </CollectionFreeFormView>
+                        <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
+                    </div>}
+                {!this.props.Document._showAudio ? (null) :
+                    <div className="formattedTextBox-dictation"
+                        onPointerDown={e => {
+                            runInAction(() => this._recording = !this._recording);
+                            setTimeout(() => this._editorView!.focus(), 500);
+                            e.stopPropagation();
+                        }} >
+                        <FontAwesomeIcon className="formattedTExtBox-audioFont"
+                            style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+                    </div>}
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
new file mode 100644
index 000000000..2dd63ec21
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -0,0 +1,33 @@
+.FormattedTextBox-tooltip {
+    position: absolute;
+    pointer-events: none;
+    z-index: 20;
+    background: white;
+    border: 1px solid silver;
+    border-radius: 2px;
+    margin-bottom: 7px;
+    -webkit-transform: translateX(-50%);
+    transform: translateX(-50%);
+  }
+  .FormattedTextBox-tooltip:before {
+    content: "";
+    height: 0; width: 0;
+    position: absolute;
+    left: 50%;
+    margin-left: -5px;
+    bottom: -6px;
+    border: 5px solid transparent;
+    border-bottom-width: 0;
+    border-top-color: silver;
+  }
+  .FormattedTextBox-tooltip:after {
+    content: "";
+    height: 0; width: 0;
+    position: absolute;
+    left: 50%;
+    margin-left: -5px;
+    bottom: -4.5px;
+    border: 5px solid transparent;
+    border-bottom-width: 0;
+    border-top-color: white;
+  }
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
new file mode 100644
index 000000000..f9e4c5210
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -0,0 +1,236 @@
+import { Mark, ResolvedPos } from "prosemirror-model";
+import { EditorState, Plugin } from "prosemirror-state";
+import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
+import { Doc, DocCastAsync } from "../../../../new_fields/Doc";
+import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { schema } from "./schema_rts";
+import { Transform } from "../../../util/Transform";
+import { ContentFittingDocumentView } from "../ContentFittingDocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import './FormattedTextBoxComment.scss';
+import React = require("react");
+import { Docs } from "../../../documents/Documents";
+import wiki from "wikijs";
+import { DocumentType } from "../../../documents/DocumentTypes";
+
+export let formattedTextBoxCommentPlugin = new Plugin({
+    view(editorView) { return new FormattedTextBoxComment(editorView); }
+});
+export function findOtherUserMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.attrs.userid);
+}
+export function findLinkMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.type === schema.marks.link);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+    let before = 0;
+    let nbef = rpos.nodeBefore;
+    while (nbef && finder(nbef.marks)) {
+        before += nbef.nodeSize;
+        rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
+        rpos && (nbef = rpos.nodeBefore);
+    }
+    return before;
+}
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+    let after = 0;
+    let naft = rpos.nodeAfter;
+    while (naft && finder(naft.marks)) {
+        after += naft.nodeSize;
+        rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
+        rpos && (naft = rpos.nodeAfter);
+    }
+    return after;
+}
+
+
+export class FormattedTextBoxComment {
+    static tooltip: HTMLElement;
+    static tooltipText: HTMLElement;
+    static tooltipInput: HTMLInputElement;
+    static start: number;
+    static end: number;
+    static mark: Mark;
+    static textBox: FormattedTextBox | undefined;
+    static linkDoc: Doc | undefined;
+    constructor(view: any) {
+        if (!FormattedTextBoxComment.tooltip) {
+            const root = document.getElementById("root");
+            FormattedTextBoxComment.tooltipInput = document.createElement("input");
+            FormattedTextBoxComment.tooltipInput.type = "checkbox";
+            FormattedTextBoxComment.tooltip = document.createElement("div");
+            FormattedTextBoxComment.tooltipText = document.createElement("div");
+            FormattedTextBoxComment.tooltipText.style.width = "100%";
+            FormattedTextBoxComment.tooltipText.style.height = "100%";
+            FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
+            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
+            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
+            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
+            FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+            FormattedTextBoxComment.tooltip.style.width = "100%";
+            FormattedTextBoxComment.tooltip.style.height = "100%";
+            FormattedTextBoxComment.tooltip.style.overflow = "hidden";
+            FormattedTextBoxComment.tooltip.style.display = "none";
+            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
+            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
+                const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
+                const textBox = FormattedTextBoxComment.textBox;
+                if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
+                    if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                    } else {
+                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+                            (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : 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 }), "onRight");
+                }
+                keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
+                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
+                e.stopPropagation();
+                e.preventDefault();
+            };
+            root && root.appendChild(FormattedTextBoxComment.tooltip);
+        }
+    }
+
+    public static Hide() {
+        FormattedTextBoxComment.textBox = undefined;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+    }
+    public static SetState(textBox: any, start: number, end: number, mark: Mark) {
+        FormattedTextBoxComment.textBox = textBox;
+        FormattedTextBoxComment.start = start;
+        FormattedTextBoxComment.end = end;
+        FormattedTextBoxComment.mark = mark;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
+    }
+
+    static update(view: EditorView, lastState?: EditorState) {
+        const state = view.state;
+        // Don't do anything if the document/selection didn't change
+        if (lastState && lastState.doc.eq(state.doc) &&
+            lastState.selection.eq(state.selection)) {
+            return;
+        }
+        FormattedTextBoxComment.linkDoc = undefined;
+
+        const textBox = FormattedTextBoxComment.textBox;
+        if (!textBox || !textBox.props) {
+            return;
+        }
+        let set = "none";
+        let nbef = 0;
+        FormattedTextBoxComment.tooltipInput.style.display = "none";
+        FormattedTextBoxComment.tooltip.style.width = "";
+        FormattedTextBoxComment.tooltip.style.height = "";
+        (FormattedTextBoxComment.tooltipText as any).href = "";
+        FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
+        FormattedTextBoxComment.tooltipText.style.overflow = "";
+        // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
+        if (state.selection.$from) {
+            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+            const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
+            const noselection = view.state.selection.$from === view.state.selection.$to;
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            const mark = child && findOtherUserMark(child.marks);
+            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
+                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+            }
+            if (mark && child && ((nbef && naft) || !noselection)) {
+                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
+                set = "";
+                FormattedTextBoxComment.tooltipInput.style.display = "";
+            }
+        }
+        // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
+        if (set === "none" && state.selection.$from) {
+            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+            const naft = findEndOfMark(state.selection.$from, view, findLinkMark);
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            const mark = child && findLinkMark(child.marks);
+            if (mark && child && nbef && naft && mark.attrs.showPreview) {
+                FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
+                (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+                if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
+                    wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
+                } else {
+                    FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
+                    FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
+                }
+                if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    FormattedTextBoxComment.tooltipText.textContent = "target not found...";
+                    (FormattedTextBoxComment.tooltipText as any).href = "";
+                    const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    try {
+                        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+                    } catch (e) { }
+                    docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
+                        if (linkDoc instanceof Doc) {
+                            (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+                            FormattedTextBoxComment.linkDoc = linkDoc;
+                            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+                            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+                            if (anchor !== target && anchor && target) {
+                                target.scrollY = NumCast(anchor?.y);
+                            }
+                            if (target) {
+                                ReactDOM.render(<ContentFittingDocumentView
+                                    Document={target}
+                                    LibraryPath={emptyPath}
+                                    fitToBox={true}
+                                    moveDocument={returnFalse}
+                                    rootSelected={returnFalse}
+                                    getTransform={Transform.Identity}
+                                    active={returnFalse}
+                                    addDocument={returnFalse}
+                                    removeDocument={returnFalse}
+                                    addDocTab={returnFalse}
+                                    pinToPres={returnFalse}
+                                    dontRegisterView={true}
+                                    renderDepth={1}
+                                    PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
+                                    PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+                                    focus={emptyFunction}
+                                    whenActiveChanged={returnFalse}
+                                />, FormattedTextBoxComment.tooltipText);
+                                FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
+                                FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
+                            }
+                            // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field.  probably need an 'overviewLayout' and then just display the target Document ....
+                            // let text = ext && StrCast(ext.text);
+                            // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
+                        }
+                    });
+                }
+                set = "";
+            }
+        }
+        if (set !== "none") {
+            // These are in screen coordinates
+            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+            const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+            // The box in which the tooltip is positioned, to use as base
+            const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+            // Find a center-ish x position from the selection endpoints (when
+            // crossing lines, end may be more to the left)
+            const left = Math.max((start.left + end.left) / 2, start.left + 3);
+            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+        }
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+    }
+
+    destroy() { }
+}
diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx
new file mode 100644
index 000000000..8f98da0fd
--- /dev/null
+++ b/src/client/views/nodes/formattedText/ImageResizeView.tsx
@@ -0,0 +1,138 @@
+import { NodeSelection } from "prosemirror-state";
+import { Doc } from "../../../../new_fields/Doc";
+import { DocServer } from "../../../DocServer";
+import { DocumentManager } from "../../../util/DocumentManager";
+import React = require("react");
+
+import { schema } from "./schema_rts";
+
+interface IImageResizeView {
+    node: any;
+    view: any;
+    getPos: any;
+    addDocTab: any;
+}
+
+export class ImageResizeView extends React.Component<IImageResizeView> {
+    constructor(props: IImageResizeView) {
+        super(props);
+    }
+
+    onClickImg = (e: any) => {
+        e.stopPropagation();
+        e.preventDefault();
+        if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) {
+            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2))));
+        }
+    }
+
+    onPointerDownImg = (e: any) => {
+        if (e.ctrlKey) {
+            e.preventDefault();
+            e.stopPropagation();
+            DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc =>
+                (linkDoc instanceof Doc) &&
+                DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document,
+                    document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false));
+        }
+    }
+
+    onPointerDownHandle = (e: any) => {
+        e.preventDefault();
+        e.stopPropagation();
+        const elementImage = document.getElementById("imageId") as HTMLElement;
+        const wid = Number(getComputedStyle(elementImage).width.replace(/px/, ""));
+        const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, ""));
+        const startX = e.pageX;
+        const startWidth = parseFloat(this.props.node.attrs.width);
+
+        const onpointermove = (e: any) => {
+            const elementOuter = document.getElementById("outerId") as HTMLElement;
+
+            const currentX = e.pageX;
+            const diffInPx = currentX - startX;
+            elementOuter.style.width = `${startWidth + diffInPx}`;
+            elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
+        };
+
+        const onpointerup = () => {
+            document.removeEventListener("pointermove", onpointermove);
+            document.removeEventListener("pointerup", onpointerup);
+            const pos = this.props.view.state.selection.from;
+            const elementOuter = document.getElementById("outerId") as HTMLElement;
+            this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height }));
+            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos))));
+        };
+
+        document.addEventListener("pointermove", onpointermove);
+        document.addEventListener("pointerup", onpointerup);
+    }
+
+    selectNode() {
+        const elementImage = document.getElementById("imageId") as HTMLElement;
+        const elementHandle = document.getElementById("handleId") as HTMLElement;
+
+        elementImage.classList.add("ProseMirror-selectednode");
+        elementHandle.style.display = "";
+    }
+
+    deselectNode() {
+        const elementImage = document.getElementById("imageId") as HTMLElement;
+        const elementHandle = document.getElementById("handleId") as HTMLElement;
+
+        elementImage.classList.remove("ProseMirror-selectednode");
+        elementHandle.style.display = "none";
+    }
+
+
+    render() {
+
+        const outerStyle = {
+            width: this.props.node.attrs.width,
+            height: this.props.node.attrs.height,
+            display: "inline-block",
+            overflow: "hidden",
+            float: this.props.node.attrs.float
+        };
+
+        const imageStyle = {
+            width: "100%",
+        };
+
+        const handleStyle = {
+            position: "absolute",
+            width: "20px",
+            heiht: "20px",
+            backgroundColor: "blue",
+            borderRadius: "15px",
+            display: "none",
+            bottom: "-10px",
+            right: "-10px"
+
+        };
+
+
+
+        return (
+            <div id="outer"
+                style={outerStyle}
+            >
+                <img
+                    id="imageId"
+                    style={imageStyle}
+                    src={this.props.node.src}
+                    onClick={this.onClickImg}
+                    onPointerDown={this.onPointerDownImg}
+
+                >
+                </img>
+                <span
+                    id="handleId"
+                    onPointerDown={this.onPointerDownHandle}
+                >
+
+                </span>
+            </div >
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
new file mode 100644
index 000000000..d80e64634
--- /dev/null
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -0,0 +1,143 @@
+import clamp from '../../../util/clamp';
+import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
+import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
+import { Node, DOMOutputSpec } from 'prosemirror-model';
+
+//import type { NodeSpec } from './Types';
+type NodeSpec = {
+    attrs?: { [key: string]: any },
+    content?: string,
+    draggable?: boolean,
+    group?: string,
+    inline?: boolean,
+    name?: string,
+    parseDOM?: Array<any>,
+    toDOM?: (node: any) => DOMOutputSpec,
+};
+
+// This assumes that every 36pt maps to one indent level.
+export const INDENT_MARGIN_PT_SIZE = 36;
+export const MIN_INDENT_LEVEL = 0;
+export const MAX_INDENT_LEVEL = 7;
+export const ATTRIBUTE_INDENT = 'data-indent';
+
+export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
+
+const ALIGN_PATTERN = /(left|right|center|justify)/;
+
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+const ParagraphNodeSpec: NodeSpec = {
+    attrs: {
+        align: { default: null },
+        color: { default: null },
+        id: { default: null },
+        indent: { default: null },
+        inset: { default: null },
+        lineSpacing: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingBottom: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingTop: { default: null },
+    },
+    content: 'inline*',
+    group: 'block',
+    parseDOM: [{ tag: 'p', getAttrs }],
+    toDOM,
+};
+
+function getAttrs(dom: HTMLElement): Object {
+    const {
+        lineHeight,
+        textAlign,
+        marginLeft,
+        paddingTop,
+        paddingBottom,
+    } = dom.style;
+
+    let align = dom.getAttribute('align') || textAlign || '';
+    align = ALIGN_PATTERN.test(align) ? align : "";
+
+    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+
+    if (!indent && marginLeft) {
+        indent = convertMarginLeftToIndentValue(marginLeft);
+    }
+
+    indent = indent || MIN_INDENT_LEVEL;
+
+    const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null;
+
+    const id = dom.getAttribute('id') || '';
+    return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
+}
+
+function toDOM(node: Node): DOMOutputSpec {
+    const {
+        align,
+        indent,
+        inset,
+        lineSpacing,
+        paddingTop,
+        paddingBottom,
+        id,
+    } = node.attrs;
+    const attrs: { [key: string]: any } | null = {};
+
+    let style = '';
+    if (align && align !== 'left') {
+        style += `text-align: ${align};`;
+    }
+
+    if (lineSpacing) {
+        const cssLineSpacing = toCSSLineSpacing(lineSpacing);
+        style +=
+            `line-height: ${cssLineSpacing};` +
+            // This creates the local css variable `--czi-content-line-height`
+            // that its children may apply.
+            `--czi-content-line-height: ${cssLineSpacing}`;
+    }
+
+    if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) {
+        style += `padding-top: ${paddingTop};`;
+    }
+
+    if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) {
+        style += `padding-bottom: ${paddingBottom};`;
+    }
+
+    if (indent) {
+        style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
+    }
+
+    if (inset) {
+        style += `margin-left: ${inset}; margin-right: ${inset};`;
+    }
+
+    style && (attrs.style = style);
+
+    if (indent) {
+        attrs[ATTRIBUTE_INDENT] = String(indent);
+    }
+
+    if (id) {
+        attrs.id = id;
+    }
+
+    return ['p', attrs, 0];
+}
+
+export const toParagraphDOM = toDOM;
+export const getParagraphNodeAttrs = getAttrs;
+
+export function convertMarginLeftToIndentValue(marginLeft: string): number {
+    const ptValue = convertToCSSPTValue(marginLeft);
+    return clamp(
+        MIN_INDENT_LEVEL,
+        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
+        MAX_INDENT_LEVEL
+    );
+}
+
+export default ParagraphNodeSpec;
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
new file mode 100644
index 000000000..a0b02880e
--- /dev/null
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -0,0 +1,241 @@
+import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { undoInputRule } from "prosemirror-inputrules";
+import { Schema } from "prosemirror-model";
+import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
+import { splitListItem, wrapInList, } from "prosemirror-schema-list";
+import { EditorState, Transaction, TextSelection } from "prosemirror-state";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { Docs } from "../../../documents/Documents";
+import { NumCast, BoolCast, Cast, StrCast } from "../../../../new_fields/Types";
+import { Doc } from "../../../../new_fields/Doc";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { Id } from "../../../../new_fields/FieldSymbols";
+
+const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+
+export type KeyMap = { [key: string]: any };
+
+export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => {
+    let fontSize: number | undefined = undefined;
+    tx2.doc.descendants((node: any, offset: any, index: any) => {
+        if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
+            const path = (tx2.doc.resolve(offset) as any).path;
+            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+            if (node.type === schema.nodes.ordered_list) depth++;
+            fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
+            const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
+            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+        }
+    });
+    return tx2;
+};
+export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+    const keys: { [key: string]: any } = {};
+
+    function bind(key: string, cmd: any) {
+        if (mapKeys) {
+            const mapped = mapKeys[key];
+            if (mapped === false) return;
+            if (mapped) key = mapped;
+        }
+        keys[key] = cmd;
+    }
+
+    bind("Mod-z", undo);
+    bind("Shift-Mod-z", redo);
+    bind("Backspace", undoInputRule);
+
+    !mac && bind("Mod-y", redo);
+
+    bind("Alt-ArrowUp", joinUp);
+    bind("Alt-ArrowDown", joinDown);
+    bind("Mod-BracketLeft", lift);
+    bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+        (document.activeElement as any).blur?.();
+        SelectionManager.DeselectAll();
+    });
+
+    bind("Mod-b", toggleMark(schema.marks.strong));
+    bind("Mod-B", toggleMark(schema.marks.strong));
+
+    bind("Mod-e", toggleMark(schema.marks.em));
+    bind("Mod-E", toggleMark(schema.marks.em));
+
+    bind("Mod-u", toggleMark(schema.marks.underline));
+    bind("Mod-U", toggleMark(schema.marks.underline));
+
+    bind("Mod-`", toggleMark(schema.marks.code));
+
+    bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
+
+    bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
+
+    bind("Ctrl->", wrapIn(schema.nodes.blockquote));
+
+    // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+    //     let newNode = schema.nodes.footnote.create({});
+    //     if (dispatch && state.selection.from === state.selection.to) {
+    //         let tr = state.tr;
+    //         tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
+    //         dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
+    //             tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+    //                 tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
+    //         return true;
+    //     }
+    //     return false;
+    // });
+
+
+    const cmd = chainCommands(exitCode, (state, dispatch) => {
+        if (dispatch) {
+            dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+            return true;
+        }
+        return false;
+    });
+    bind("Mod-Enter", cmd);
+    bind("Shift-Enter", cmd);
+    mac && bind("Ctrl-Enter", cmd);
+
+
+    bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
+
+    bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+
+    for (let i = 1; i <= 6; i++) {
+        bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+    }
+
+    const hr = schema.nodes.horizontal_rule;
+    bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+        return true;
+    });
+
+    bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const ref = state.selection;
+        const range = ref.$from.blockRange(ref.$to);
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
+            const tx3 = updateBullets(tx2, schema);
+            marks && tx3.ensureMarks([...marks]);
+            marks && tx3.setStoredMarks([...marks]);
+            dispatch(tx3);
+        })) { // couldn't sink into an existing list, so wrap in a new one
+            const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
+            if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
+                const tx3 = updateBullets(tx2, schema);
+                // when promoting to a list, assume list will format things so don't copy the stored marks.
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+                dispatch(tx3);
+            })) {
+                console.log("bullet promote fail");
+            }
+        }
+    });
+
+    bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+
+        if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+            const tx3 = updateBullets(tx2, schema);
+            marks && tx3.ensureMarks([...marks]);
+            marks && tx3.setStoredMarks([...marks]);
+            dispatch(tx3);
+        })) {
+            console.log("bullet demote fail");
+        }
+    });
+    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const layoutDoc = props.Document;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
+        if (originalDoc instanceof Doc) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
+            const newDoc = Docs.Create.TextDocument("", {
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
+                x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
+            });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
+            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            props.addDocument(newDoc);
+        }
+    });
+
+    const splitMetadata = (marks: any, tx: Transaction) => {
+        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+        return tx;
+    };
+    const addTextOnRight = (force: boolean) => {
+        const layoutDoc = props.Document;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
+        if (force || props.Document._singleLine) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
+            const newDoc = Docs.Create.TextDocument("", {
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
+                x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
+            });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
+            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            props.addDocument(newDoc);
+            return true;
+        }
+        return false;
+    };
+    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+        return addTextOnRight(true);
+    });
+    bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+        if (addTextOnRight(false)) return true;
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        if (!splitListItem(schema.nodes.list_item)(state, dispatch)) {
+            if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
+                splitMetadata(marks, tx3);
+                if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
+                    dispatch(tx3);
+                }
+            })) {
+                return false;
+            }
+        }
+        return true;
+    });
+    bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        dispatch(splitMetadata(marks, state.tr));
+        return false;
+    });
+    bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
+            return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
+        });
+        const path = (state.doc.resolve(state.selection.from - 1) as any).path;
+        const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
+        const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator;
+        if (anchor >= 0) {
+            const textsel = TextSelection.create(state.doc, anchor, range!.end);
+            const text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
+            let whitespace = text.length - 1;
+            for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
+            if (text.endsWith(":")) {
+                dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
+                    addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
+            }
+        }
+        return false;
+    });
+
+
+    return keys;
+}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
new file mode 100644
index 000000000..36da769c3
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -0,0 +1,121 @@
+@import "../../globalCssVariables";
+
+.button-dropdown-wrapper {
+    position: relative;
+
+    .dropdown-button {
+        width: 15px;
+        padding-left: 5px;
+        padding-right: 5px;
+    }
+
+    .dropdown-button-combined {
+        width: 50px;
+        display: flex;
+        justify-content: space-between;
+
+        svg:nth-child(2) {
+            margin-top: 2px;
+        }
+    }
+
+    .dropdown {
+        position: absolute;
+        top: 35px;
+        left: 0;
+        background-color: #323232;
+        color: $light-color-secondary;
+        border: 1px solid #4d4d4d;
+        border-radius: 0 6px 6px 6px;
+        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+        min-width: 150px;
+        padding: 5px;
+        font-size: 12px;
+        z-index: 10001;
+
+        button {
+            background-color: #323232;
+            border: 1px solid black;
+            border-radius: 1px;
+            padding: 6px;
+            margin: 5px 0;
+            font-size: 10px;
+
+            &:hover {
+                background-color: black;
+            }
+
+            &:last-child {
+                margin-bottom: 0;
+            }
+        }
+    }
+
+    input {
+        color: black;
+    }
+}
+
+.link-menu {
+    .divider {
+        background-color: white;
+        height: 1px;
+        width: 100%;
+    }
+}
+
+.color-preview-button {
+    .color-preview {
+        width: 100%;
+        height: 3px;
+        margin-top: 3px;
+    }
+}
+
+.color-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    button.color-button {
+        width: 20px;
+        height: 20px;
+        border-radius: 15px !important;
+        margin: 3px;
+        border: 2px solid transparent !important;
+        padding: 3px;
+
+        &.active {
+            border: 2px solid white !important;
+        }
+    }
+}
+
+select {
+    background-color: #323232;
+    color: white;
+    border: 1px solid black;
+    // border-top: none;
+    // border-bottom: none;
+    font-size: 12px;
+    height: 100%;
+    margin-right: 3px;
+
+    &:focus,
+    &:hover {
+        background-color: black;
+    }
+
+    &::-ms-expand {
+        color: white;
+    }
+}
+
+.row-2 {
+    display: flex;
+    justify-content: space-between;
+
+    >div {
+        display: flex;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
new file mode 100644
index 000000000..cc04e0d6d
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -0,0 +1,875 @@
+import React = require("react");
+import AntimodeMenu from "../../AntimodeMenu";
+import { observable, action, } from "mobx";
+import { observer } from "mobx-react";
+import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
+import { schema } from "./schema_rts";
+import { EditorView } from "prosemirror-view";
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
+import { updateBullets } from "./ProsemirrorExampleTransfer";
+import { FieldViewProps } from "../FieldView";
+import { Cast, StrCast } from "../../../../new_fields/Types";
+import { FormattedTextBoxProps } from "./FormattedTextBox";
+import { unimplementedFunction, Utils } from "../../../../Utils";
+import { wrapInList } from "prosemirror-schema-list";
+import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../new_fields/SchemaHeaderField';
+import "./RichTextMenu.scss";
+import { DocServer } from "../../../DocServer";
+import { Doc } from "../../../../new_fields/Doc";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { LinkManager } from "../../../util/LinkManager";
+const { toggleMark, setBlockType } = require("prosemirror-commands");
+
+library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
+
+@observer
+export default class RichTextMenu extends AntimodeMenu {
+    static Instance: RichTextMenu;
+    public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
+
+    private view?: EditorView;
+    public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+
+    public _brushMap: Map<string, Set<Mark>> = new Map();
+    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
+    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
+    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
+    private fontColors: (string | undefined)[];
+    private highlightColors: (string | undefined)[];
+
+    @observable private collapsed: boolean = false;
+    @observable private boldActive: boolean = false;
+    @observable private italicsActive: boolean = false;
+    @observable private underlineActive: boolean = false;
+    @observable private strikethroughActive: boolean = false;
+    @observable private subscriptActive: boolean = false;
+    @observable private superscriptActive: boolean = false;
+
+    @observable private activeFontSize: string = "";
+    @observable private activeFontFamily: string = "";
+    @observable private activeListType: string = "";
+
+    @observable private brushIsEmpty: boolean = true;
+    @observable private brushMarks: Set<Mark> = new Set();
+    @observable private showBrushDropdown: boolean = false;
+
+    @observable private activeFontColor: string = "black";
+    @observable private showColorDropdown: boolean = false;
+
+    @observable private activeHighlightColor: string = "transparent";
+    @observable private showHighlightDropdown: boolean = false;
+
+    @observable private currentLink: string | undefined = "";
+    @observable private showLinkDropdown: boolean = false;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        RichTextMenu.Instance = this;
+        this._canFade = false;
+
+        this.fontSizeOptions = [
+            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
+            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
+        ];
+
+        this.fontFamilyOptions = [
+            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
+            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
+        ];
+
+        this.listTypeOptions = [
+            { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType },
+            { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
+        ];
+
+        this.fontColors = [
+            DarkPastelSchemaPalette.get("pink2"),
+            DarkPastelSchemaPalette.get("purple4"),
+            DarkPastelSchemaPalette.get("bluegreen1"),
+            DarkPastelSchemaPalette.get("yellow4"),
+            DarkPastelSchemaPalette.get("red2"),
+            DarkPastelSchemaPalette.get("bluegreen7"),
+            DarkPastelSchemaPalette.get("bluegreen5"),
+            DarkPastelSchemaPalette.get("orange1"),
+            "#757472",
+            "#000"
+        ];
+
+        this.highlightColors = [
+            PastelSchemaPalette.get("pink2"),
+            PastelSchemaPalette.get("purple4"),
+            PastelSchemaPalette.get("bluegreen1"),
+            PastelSchemaPalette.get("yellow4"),
+            PastelSchemaPalette.get("red2"),
+            PastelSchemaPalette.get("bluegreen7"),
+            PastelSchemaPalette.get("bluegreen5"),
+            PastelSchemaPalette.get("orange1"),
+            "white",
+            "transparent"
+        ];
+    }
+
+    @action
+    changeView(view: EditorView) {
+        this.view = view;
+    }
+
+    update(view: EditorView, lastState: EditorState | undefined) {
+        this.updateFromDash(view, lastState, this.editorProps);
+    }
+
+
+    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
+        if (this.view) {
+            const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
+            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
+                addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+            return this.view.state.selection.$from.nodeAfter?.text || "";
+        }
+        return "";
+    }
+
+    @action
+    public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
+        if (!view) {
+            console.log("no editor?  why?");
+            return;
+        }
+        this.view = view;
+        const state = view.state;
+        props && (this.editorProps = props);
+
+        // Don't do anything if the document/selection didn't change
+        if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
+
+        // update active marks
+        const activeMarks = this.getActiveMarksOnSelection();
+        this.setActiveMarkButtons(activeMarks);
+
+        // update active font family and size
+        const active = this.getActiveFontStylesOnSelection();
+        const activeFamilies = active && active.get("families");
+        const activeSizes = active && active.get("sizes");
+
+        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+
+        // update link in current selection
+        const targetTitle = await this.getTextLinkTargetTitle();
+        this.setCurrentLink(targetTitle);
+    }
+
+    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+        if (mark) {
+            const node = (state.selection as NodeSelection).node;
+            if (node?.type === schema.nodes.ordered_list) {
+                let attrs = node.attrs;
+                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
+                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
+                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
+            } else {
+                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
+                    const { from, $from, to, empty } = tx.selection;
+                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+                    } else dispatch(tx);
+                });
+            }
+        }
+    }
+
+    // finds font sizes and families in selection
+    getActiveFontStylesOnSelection() {
+        if (!this.view) return;
+
+        const activeFamilies: string[] = [];
+        const activeSizes: string[] = [];
+        const state = this.view.state;
+        const pos = this.view.state.selection.$from;
+        const ref_node = this.reference_node(pos);
+        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+            ref_node.marks.forEach(m => {
+                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+            });
+        }
+
+        const styles = new Map<String, String[]>();
+        styles.set("families", activeFamilies);
+        styles.set("sizes", activeSizes);
+        return styles;
+    }
+
+    getMarksInSelection(state: EditorState<any>) {
+        const found = new Set<Mark>();
+        const { from, to } = state.selection as TextSelection;
+        state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+        return found;
+    }
+
+    //finds all active marks on selection in given group
+    getActiveMarksOnSelection() {
+        if (!this.view) return;
+
+        const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
+        //current selection
+        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
+        const state = this.view.state;
+        let activeMarks: MarkType[] = [];
+        if (!empty) {
+            activeMarks = markGroup.filter(mark => {
+                const has = false;
+                for (let i = 0; !has && i < ranges.length; i++) {
+                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
+                }
+                return false;
+            });
+        }
+        else {
+            const pos = this.view.state.selection.$from;
+            const ref_node: ProsNode | null = this.reference_node(pos);
+            if (ref_node !== null && ref_node !== this.view.state.doc) {
+                if (ref_node.isText) {
+                }
+                else {
+                    return [];
+                }
+                activeMarks = markGroup.filter(mark_type => {
+                    if (mark_type === state.schema.marks.pFontSize) {
+                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
+                    }
+                    const mark = state.schema.mark(mark_type);
+                    return ref_node.marks.includes(mark);
+                });
+            }
+        }
+        return activeMarks;
+    }
+
+    destroy() {
+        this.fadeOut(true);
+    }
+
+    @action
+    setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
+        if (!activeMarks) return;
+
+        this.boldActive = false;
+        this.italicsActive = false;
+        this.underlineActive = false;
+        this.strikethroughActive = false;
+        this.subscriptActive = false;
+        this.superscriptActive = false;
+
+        activeMarks.forEach(mark => {
+            switch (mark.name) {
+                case "strong": this.boldActive = true; break;
+                case "em": this.italicsActive = true; break;
+                case "underline": this.underlineActive = true; break;
+                case "strikethrough": this.strikethroughActive = true; break;
+                case "subscript": this.subscriptActive = true; break;
+                case "superscript": this.superscriptActive = true; break;
+            }
+        });
+    }
+
+    createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
+        const self = this;
+        function onClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && command && command(self.view.state, self.view.dispatch, self.view);
+            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
+            self.setActiveMarkButtons(self.getActiveMarksOnSelection());
+        }
+
+        return (
+            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
+                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+            </button>
+        );
+    }
+
+    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+        const items = options.map(({ title, label, hidden, style }) => {
+            if (hidden) {
+                return label === activeOption ?
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+            }
+            return label === activeOption ?
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+        });
+
+        const self = this;
+        function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
+            e.stopPropagation();
+            e.preventDefault();
+            options.forEach(({ label, mark, command }) => {
+                if (e.target.value === label) {
+                    self.view && mark && command(mark, self.view);
+                }
+            });
+        }
+        return <select onChange={onChange} key={key}>{items}</select>;
+    }
+
+    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+        const items = options.map(({ title, label, hidden, style }) => {
+            if (hidden) {
+                return label === activeOption ?
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+            }
+            return label === activeOption ?
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+        });
+
+        const self = this;
+        function onChange(val: string) {
+            options.forEach(({ label, node, command }) => {
+                if (val === label) {
+                    self.view && node && command(node);
+                }
+            });
+        }
+        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
+    }
+
+    changeFontSize = (mark: Mark, view: EditorView) => {
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
+    }
+
+    changeFontFamily = (mark: Mark, view: EditorView) => {
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
+    }
+
+    // TODO: remove doesn't work
+    //remove all node type and apply the passed-in one to the selected text
+    changeListType = (nodeType: NodeType | undefined) => {
+        if (!this.view) return;
+
+        if (nodeType === schema.nodes.bullet_list) {
+            wrapInList(nodeType)(this.view.state, this.view.dispatch);
+        } else {
+            const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
+            if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+
+                this.view!.dispatch(tx2);
+            })) {
+                const tx2 = this.view.state.tr;
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+
+                this.view.dispatch(tx3);
+            }
+        }
+    }
+
+    insertSummarizer(state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        const mark = state.schema.marks.summarize.create();
+        const tr = state.tr;
+        tr.addMark(state.selection.from, state.selection.to, mark);
+        const content = tr.selection.content();
+        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        return true;
+    }
+
+    @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+
+    // todo: add brushes to brushMap to save with a style name
+    onBrushNameKeyPress = (e: React.KeyboardEvent) => {
+        if (e.key === "Enter") {
+            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+            this._brushNameRef.current!.style.background = "lightGray";
+        }
+    }
+    _brushNameRef = React.createRef<HTMLInputElement>();
+
+    createBrushButton() {
+        const self = this;
+        function onBrushClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.fillBrush(self.view.state, self.view.dispatch);
+        }
+
+        let label = "Stored marks: ";
+        if (this.brushMarks && this.brushMarks.size > 0) {
+            this.brushMarks.forEach((mark: Mark) => {
+                const markType = mark.type;
+                label += markType.name;
+                label += ", ";
+            });
+            label = label.substring(0, label.length - 2);
+        } else {
+            label = "No marks are currently stored";
+        }
+
+        const button =
+            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>{label}</p>
+                <button onPointerDown={this.clearBrush}>Clear brush</button>
+                <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    @action
+    clearBrush() {
+        RichTextMenu.Instance.brushIsEmpty = true;
+        RichTextMenu.Instance.brushMarks = new Set();
+    }
+
+    @action
+    fillBrush(state: EditorState<any>, dispatch: any) {
+        if (!this.view) return;
+
+        if (this.brushIsEmpty) {
+            const selected_marks = this.getMarksInSelection(this.view.state);
+            if (selected_marks.size >= 0) {
+                this.brushMarks = selected_marks;
+                this.brushIsEmpty = !this.brushIsEmpty;
+            }
+        }
+        else {
+            const { from, to, $from } = this.view.state.selection;
+            if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
+                if (this.brushMarks && to - from > 0) {
+                    this.view.dispatch(this.view.state.tr.removeMark(from, to));
+                    Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
+                        this.setMark(mark, this.view!.state, this.view!.dispatch);
+                    });
+                }
+            }
+            else {
+                this.brushIsEmpty = !this.brushIsEmpty;
+            }
+        }
+    }
+
+    @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
+    @action setActiveColor(color: string) { this.activeFontColor = color; }
+
+    createColorButton() {
+        const self = this;
+        function onColorClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+        }
+        function changeColor(e: React.PointerEvent, color: string) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.setActiveColor(color);
+            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+        }
+
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+                <FontAwesomeIcon icon="palette" size="lg" />
+                <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown" >
+                <p>Change font color:</p>
+                <div className="color-wrapper">
+                    {this.fontColors.map(color => {
+                        if (color) {
+                            return this.activeFontColor === color ?
+                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                        }
+                    })}
+                </div>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
+        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+        if (state.selection.empty) {
+            dispatch(state.tr.addStoredMark(colorMark));
+            return false;
+        }
+        this.setMark(colorMark, state, dispatch);
+    }
+
+    @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
+    @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+
+    createHighlighterButton() {
+        const self = this;
+        function onHighlightClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+        }
+        function changeHighlight(e: React.PointerEvent, color: string) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.setActiveHighlight(color);
+            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+        }
+
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
+                <FontAwesomeIcon icon="highlighter" size="lg" />
+                <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>Change highlight color:</p>
+                <div className="color-wrapper">
+                    {this.highlightColors.map(color => {
+                        if (color) {
+                            return this.activeHighlightColor === color ?
+                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                        }
+                    })}
+                </div>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+    }
+
+    @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
+    @action setCurrentLink(link: string) { this.currentLink = link; }
+
+    createLinkButton() {
+        const self = this;
+
+        function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
+            self.setCurrentLink(e.target.value);
+        }
+
+        const link = this.currentLink ? this.currentLink : "";
+
+        const button = <FontAwesomeIcon icon="link" size="lg" />;
+
+        const dropdownContent =
+            <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>
+                <div className="divider"></div>
+                <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
+        );
+    }
+
+    async getTextLinkTargetTitle() {
+        if (!this.view) return;
+
+        const node = this.view.state.selection.$from.nodeAfter;
+        const link = node && node.marks.find(m => m.type.name === "link");
+        if (link) {
+            const href = link.attrs.href;
+            if (href) {
+                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    if (linkclicked) {
+                        const linkDoc = await DocServer.GetRefField(linkclicked);
+                        if (linkDoc instanceof Doc) {
+                            const anchor1 = await Cast(linkDoc.anchor1, Doc);
+                            const anchor2 = await Cast(linkDoc.anchor2, Doc);
+                            const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+                            if (currentDoc && anchor1 && anchor2) {
+                                if (Doc.AreProtosEqual(currentDoc, anchor1)) {
+                                    return StrCast(anchor2.title);
+                                }
+                                if (Doc.AreProtosEqual(currentDoc, anchor2)) {
+                                    return StrCast(anchor1.title);
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    return href;
+                }
+            } else {
+                return link.attrs.title;
+            }
+        }
+    }
+
+    // TODO: should check for valid URL
+    makeLinkToURL = (target: String, lcoation: string) => {
+        if (!this.view) return;
+
+        let node = this.view.state.selection.$from.nodeAfter;
+        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
+        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+        node = this.view.state.selection.$from.nodeAfter;
+        link = node && node.marks.find(m => m.type.name === "link");
+    }
+
+    deleteLink = () => {
+        if (!this.view) return;
+
+        const node = this.view.state.selection.$from.nodeAfter;
+        const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
+        const href = link!.attrs.href;
+        if (href) {
+            if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                if (linkclicked) {
+                    DocServer.GetRefField(linkclicked).then(async linkDoc => {
+                        if (linkDoc instanceof Doc) {
+                            LinkManager.Instance.deleteLink(linkDoc);
+                            this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
+                        }
+                    });
+                }
+            } else {
+                if (node) {
+                    const { tr, schema, selection } = this.view.state;
+                    const extension = this.linkExtend(selection.$anchor, href);
+                    this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
+                }
+            }
+        }
+    }
+
+    linkExtend($start: ResolvedPos, href: string) {
+        const mark = this.view!.state.schema.marks.link;
+
+        let startIndex = $start.index();
+        let endIndex = $start.indexAfter();
+
+        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
+        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
+
+        let startPos = $start.start();
+        let endPos = startPos;
+        for (let i = 0; i < endIndex; i++) {
+            const size = $start.parent.child(i).nodeSize;
+            if (i < startIndex) startPos += size;
+            endPos += size;
+        }
+        return { from: startPos, to: endPos };
+    }
+
+    reference_node(pos: ResolvedPos<any>): ProsNode | null {
+        if (!this.view) return null;
+
+        let ref_node: ProsNode = this.view.state.doc;
+        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+            ref_node = pos.nodeBefore;
+        }
+        else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+            ref_node = pos.nodeAfter;
+        }
+        else if (pos.pos > 0) {
+            let skip = false;
+            for (let i: number = pos.pos - 1; i > 0; i--) {
+                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
+                    if (node.isLeaf && !skip) {
+                        ref_node = node;
+                        skip = true;
+                    }
+
+                });
+            }
+        }
+        if (!ref_node.isLeaf && ref_node.childCount > 0) {
+            ref_node = ref_node.child(0);
+        }
+        return ref_node;
+    }
+
+    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
+    @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
+
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        this.Pinned = !this.Pinned;
+        if (!this.Pinned) {
+            this.fadeOut(true);
+        }
+    }
+
+    @action
+    protected toggleCollapse = (e: React.MouseEvent) => {
+        this.collapsed = !this.collapsed;
+        setTimeout(() => {
+            const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
+            RichTextMenu.Instance.jumpTo(x, this._top);
+        }, 0);
+    }
+
+    render() {
+
+        const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+            this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+            this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+            this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+            this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+            this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+            this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+            this.createColorButton(),
+            this.createHighlighterButton(),
+            this.createLinkButton(),
+            this.createBrushButton(),
+            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
+        ]}</div>;
+
+        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+            <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
+                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
+                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
+            </div>
+            <div key="button">
+                <div key="collapser">
+                    <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
+                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
+                    </button>
+                </div>
+                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
+                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+                </button>
+                {this.getDragger()}
+            </div>
+        </div>;
+
+        return (
+            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+                {this.getElementWithRows([row1, row2], 2, false)}
+            </div>
+        );
+    }
+}
+
+interface ButtonDropdownProps {
+    view?: EditorView;
+    button: JSX.Element;
+    dropdownContent: JSX.Element;
+    openDropdownOnButton?: boolean;
+}
+
+@observer
+class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+
+    @observable private showDropdown: boolean = false;
+    private ref: HTMLDivElement | null = null;
+
+    componentDidMount() {
+        document.addEventListener("pointerdown", this.onBlur);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onBlur);
+    }
+
+    @action
+    setShowDropdown(show: boolean) {
+        this.showDropdown = show;
+    }
+    @action
+    toggleDropdown() {
+        this.showDropdown = !this.showDropdown;
+    }
+
+    onDropdownClick = (e: React.PointerEvent) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.props.view && this.props.view.focus();
+        this.toggleDropdown();
+    }
+
+    onBlur = (e: PointerEvent) => {
+        setTimeout(() => {
+            if (this.ref !== null && !this.ref.contains(e.target as Node)) {
+                this.setShowDropdown(false);
+            }
+        }, 0);
+    }
+
+    render() {
+        return (
+            <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
+                {this.props.openDropdownOnButton ?
+                    <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
+                        {this.props.button}
+                        <FontAwesomeIcon icon="caret-down" size="sm" />
+                    </button> :
+                    <>
+                        {this.props.button}
+                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+                            <FontAwesomeIcon icon="caret-down" size="sm" />
+                        </button>
+                    </>}
+
+                {this.showDropdown ? this.props.dropdownContent : (null)}
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
new file mode 100644
index 000000000..335094e23
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -0,0 +1,319 @@
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
+import { NodeSelection, TextSelection } from "prosemirror-state";
+import { DataSym, Doc } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { Cast, NumCast } from "../../../../new_fields/Types";
+import { returnFalse, Utils } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { Docs, DocUtils } from "../../../documents/Documents";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { wrappingInputRule } from "./prosemirrorPatches";
+import RichTextMenu from "./RichTextMenu";
+import { schema } from "./schema_rts";
+
+export class RichTextRules {
+    public Document: Doc;
+    public TextBox: FormattedTextBox;
+    public EnteringStyle: boolean = false;
+    constructor(doc: Doc, textBox: FormattedTextBox) {
+        this.Document = doc;
+        this.TextBox = textBox;
+    }
+    public inpRules = {
+        rules: [
+            ...smartQuotes,
+            ellipsis,
+            emDash,
+
+            // > blockquote
+            wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
+
+            // 1. ordered list
+            wrappingInputRule(
+                /^1\.\s$/,
+                schema.nodes.ordered_list,
+                () => {
+                    return ({ mapStyle: "decimal", bulletStyle: 1 });
+                },
+                (match: any, node: any) => {
+                    return node.childCount + node.attrs.order === +match[1];
+                },
+                (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })
+            ),
+            // a. alphabbetical list
+            wrappingInputRule(
+                /^a\.\s$/,
+                schema.nodes.ordered_list,
+                // match => {
+                () => {
+                    return ({ mapStyle: "alpha", bulletStyle: 1 });
+                    // return ({ order: +match[1] })
+                },
+                (match: any, node: any) => {
+                    return node.childCount + node.attrs.order === +match[1];
+                },
+                (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } })
+            ),
+
+            // * bullet list
+            wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
+
+            // ``` code block
+            textblockTypeInputRule(/^```$/, schema.nodes.code_block),
+
+            // create an inline view of a tag stored under the '#' field
+            new InputRule(
+                new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
+                (state, match, start, end) => {
+                    const tag = match[1];
+                    if (!tag) return state.tr;
+                    this.Document[DataSym]["#"] = tag;
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+                    return state.tr.deleteRange(start, end).insert(start, fieldView);
+                }),
+
+            // # heading
+            textblockTypeInputRule(
+                new RegExp(/^(#{1,6})\s$/),
+                schema.nodes.heading,
+                match => {
+                    return ({ level: match[1].length });
+                }
+            ),
+
+            // set the font size using #<font-size> 
+            new InputRule(
+                new RegExp(/%([0-9]+)\s$/),
+                (state, match, start, end) => {
+                    const size = Number(match[1]);
+                    return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
+                }),
+
+            // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]]   // [[:Doc]] => hyperlink   [[fieldKey]] => show field   [[fieldKey:Doc]] => show field of doc
+            new InputRule(
+                new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? \-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
+                (state, match, start, end) => {
+                    const fieldKey = match[1];
+                    const docid = match[3]?.substring(1);
+                    const value = match[2]?.substring(1);
+                    if (!fieldKey) {
+                        if (docid) {
+                            DocServer.GetRefField(docid).then(docx => {
+                                const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
+                                DocUtils.Publish(target, docid, returnFalse, returnFalse);
+                                DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
+                            });
+                            const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+                            return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
+                        }
+                        return state.tr;
+                    }
+                    if (value !== "" && value !== undefined) {
+                        const num = value.match(/^[0-9.]/);
+                        this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
+                    }
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+                    return state.tr.deleteRange(start, end).insert(start, fieldView);
+                }),
+            // create an inline view of a document {{ <layoutKey> : <Doc> }}  // {{:Doc}} => show default view of document   {{<layout>}} => show layout for this doc   {{<layout> : Doc}} => show layout for another doc
+            new InputRule(
+                new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
+                (state, match, start, end) => {
+                    const fieldKey = match[1] || "";
+                    const fieldParam = match[2]?.replace("…", "...") || "";
+                    const docid = match[3]?.substring(1);
+                    if (!fieldKey && !docid) return state.tr;
+                    docid && DocServer.GetRefField(docid).then(docx => {
+                        if (!(docx instanceof Doc && docx)) {
+                            const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
+                            DocUtils.Publish(docx, docid, returnFalse, returnFalse);
+                        }
+                    });
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
+                    const sm = state.storedMarks || undefined;
+                    return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+                }),
+            new InputRule(
+                new RegExp(/>>$/),
+                (state, match, start, end) => {
+                    const textDoc = this.Document[DataSym];
+                    const numInlines = NumCast(textDoc.inlineTextCount);
+                    textDoc.inlineTextCount = numInlines + 1;
+                    const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
+                    const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" });
+                    textDocInline.title = inlineFieldKey; // give the annotation its own title
+                    textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+                    textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+                    textDocInline.proto = textDoc;  // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+                    textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+                    textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+                    textDoc[inlineFieldKey] = ""; // set a default value for the annotation
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
+                    const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" });
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced;
+                }),
+            // stop using active style
+            new InputRule(
+                new RegExp(/%%$/),
+                (state, match, start, end) => {
+                    const tr = state.tr.deleteRange(start, end);
+                    const marks = state.tr.selection.$anchor.nodeBefore?.marks;
+                    return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
+                }),
+
+            // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/[ti!x]$/),
+                (state, match, start, end) => {
+                    if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
+                    const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+                    return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+                }),
+
+            // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/(%d|d)$/),
+                (state, match, start, end) => {
+                    if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
+                    const pos = (state.doc.resolve(start) as any);
+                    for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                        const node = pos.node(depth);
+                        if (node.type === schema.nodes.paragraph) {
+                            const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
+                            const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                            return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+                        }
+                    }
+                    return null;
+                }),
+
+            // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/(%h|h)$/),
+                (state, match, start, end) => {
+                    if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
+                    const pos = (state.doc.resolve(start) as any);
+                    for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                        const node = pos.node(depth);
+                        if (node.type === schema.nodes.paragraph) {
+                            const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
+                            const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                            return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+                        }
+                    }
+                    return null;
+                }),
+            // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/(%q|q)$/),
+                (state, match, start, end) => {
+                    if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
+                    const pos = (state.doc.resolve(start) as any);
+                    if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+                        const node = state.selection.node;
+                        return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
+                    }
+                    for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                        const node = pos.node(depth);
+                        if (node.type === schema.nodes.paragraph) {
+                            const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
+                            const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                            return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+                        }
+                    }
+                    return null;
+                }),
+
+
+            // center justify text
+            new InputRule(
+                new RegExp(/%\^$/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                }),
+            // left justify text
+            new InputRule(
+                new RegExp(/%\[$/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                }),
+            // right justify text
+            new InputRule(
+                new RegExp(/%\]$/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                }),
+            new InputRule(
+                new RegExp(/%\(/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || [];
+                    const mark = state.schema.marks.summarizeInclusive.create();
+                    sm.push(mark);
+                    const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+                    const content = selected.selection.content();
+                    const replaced = node ? selected.replaceRangeWith(start, end,
+                        schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+                }),
+            new InputRule(
+                new RegExp(/%\)/),
+                (state, match, start, end) => {
+                    return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+                }),
+            new InputRule(
+                new RegExp(/%f$/),
+                (state, match, start, end) => {
+                    const newNode = schema.nodes.footnote.create({});
+                    const tr = state.tr;
+                    tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
+                    return tr.setSelection(new NodeSelection( // select the footnote node to open its display
+                        tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+                            tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
+                }),
+
+            // activate a style by name using prefix '%'
+            new InputRule(
+                new RegExp(/%[a-z]+$/),
+                (state, match, start, end) => {
+                    const color = match[0].substring(1, match[0].length);
+                    const marks = RichTextMenu.Instance._brushMap.get(color);
+                    if (marks) {
+                        const tr = state.tr.deleteRange(start, end);
+                        return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+                    }
+                    const isValidColor = (strColor: string) => {
+                        const s = new Option().style;
+                        s.color = strColor;
+                        return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+                    };
+                    if (isValidColor(color)) {
+                        return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+                    }
+                    return null;
+                }),
+        ]
+    };
+}
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
new file mode 100644
index 000000000..33caf5751
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -0,0 +1,718 @@
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
+import { StepMap } from "prosemirror-transform";
+import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { List } from "../../../../new_fields/List";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { Docs } from "../../../documents/Documents";
+import { CollectionViewType } from "../../collections/CollectionView";
+import { DocumentView } from "../DocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { Transform } from "../../../util/Transform";
+import React = require("react");
+
+import { schema } from "./schema_rts";
+
+export class OrderedListView {
+    update(node: any) {
+        return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
+    }
+}
+
+export class ImageResizeView {
+    _handle: HTMLElement;
+    _img: HTMLElement;
+    _outer: HTMLElement;
+    constructor(node: any, view: any, getPos: any, addDocTab: any) {
+        //moved
+        this._handle = document.createElement("span");
+        this._img = document.createElement("img");
+        this._outer = document.createElement("span");
+        this._outer.style.position = "relative";
+        this._outer.style.width = node.attrs.width;
+        this._outer.style.height = node.attrs.height;
+        this._outer.style.display = "inline-block";
+        this._outer.style.overflow = "hidden";
+        (this._outer.style as any).float = node.attrs.float;
+        //moved
+        this._img.setAttribute("src", node.attrs.src);
+        this._img.style.width = "100%";
+        this._handle.style.position = "absolute";
+        this._handle.style.width = "20px";
+        this._handle.style.height = "20px";
+        this._handle.style.backgroundColor = "blue";
+        this._handle.style.borderRadius = "15px";
+        this._handle.style.display = "none";
+        this._handle.style.bottom = "-10px";
+        this._handle.style.right = "-10px";
+        const self = this;
+        //moved
+        this._img.onclick = function (e: any) {
+            e.stopPropagation();
+            e.preventDefault();
+            if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
+                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
+            }
+        };
+        //moved
+        this._img.onpointerdown = function (e: any) {
+            if (e.ctrlKey) {
+                e.preventDefault();
+                e.stopPropagation();
+                DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
+                    (linkDoc instanceof Doc) &&
+                    DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
+                        document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false));
+            }
+        };
+        //moved
+        this._handle.onpointerdown = function (e: any) {
+            e.preventDefault();
+            e.stopPropagation();
+            const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
+            const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
+            const startX = e.pageX;
+            const startWidth = parseFloat(node.attrs.width);
+            const onpointermove = (e: any) => {
+                const currentX = e.pageX;
+                const diffInPx = currentX - startX;
+                self._outer.style.width = `${startWidth + diffInPx}`;
+                self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
+            };
+
+            const onpointerup = () => {
+                document.removeEventListener("pointermove", onpointermove);
+                document.removeEventListener("pointerup", onpointerup);
+                const pos = view.state.selection.from;
+                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
+                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
+            };
+
+            document.addEventListener("pointermove", onpointermove);
+            document.addEventListener("pointerup", onpointerup);
+        };
+        //Moved
+        this._outer.appendChild(this._img);
+        this._outer.appendChild(this._handle);
+        (this as any).dom = this._outer;
+    }
+
+    selectNode() {
+        this._img.classList.add("ProseMirror-selectednode");
+
+        this._handle.style.display = "";
+    }
+
+    deselectNode() {
+        this._img.classList.remove("ProseMirror-selectednode");
+
+        this._handle.style.display = "none";
+    }
+}
+
+export class DashDocCommentView {
+    _collapsed: HTMLElement;
+    _view: any;
+    constructor(node: any, view: any, getPos: any) {
+        //moved
+        this._collapsed = document.createElement("span");
+        this._collapsed.className = "formattedTextBox-inlineComment";
+        this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
+        this._view = view;
+        //moved
+        const targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+            for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
+                const m = view.state.doc.nodeAt(i);
+                if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
+                    return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+                }
+            }
+            const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
+            view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
+            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
+            return undefined;
+        };
+        //moved
+        this._collapsed.onpointerdown = (e: any) => {
+            e.stopPropagation();
+        };
+        //moved
+        this._collapsed.onpointerup = (e: any) => {
+            const target = targetNode();
+            if (target) {
+                const expand = target.hidden;
+                const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+                view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
+                setTimeout(() => {
+                    expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
+                }, 0);
+            }
+            e.stopPropagation();
+        };
+        //moved
+        this._collapsed.onpointerenter = (e: any) => {
+            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+            e.preventDefault();
+            e.stopPropagation();
+        };
+        //moved
+        this._collapsed.onpointerleave = (e: any) => {
+            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+            e.preventDefault();
+            e.stopPropagation();
+        };
+
+        (this as any).dom = this._collapsed;
+    }
+    //moved
+    selectNode() { }
+}
+
+export class DashDocView {
+    _dashSpan: HTMLDivElement;
+    _outer: HTMLElement;
+    _dashDoc: Doc | undefined;
+    _reactionDisposer: IReactionDisposer | undefined;
+    _renderDisposer: IReactionDisposer | undefined;
+    _textBox: FormattedTextBox;
+
+    getDocTransform = () => {
+        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
+        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
+    }
+    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
+
+    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._textBox = tbox;
+        this._dashSpan = document.createElement("div");
+        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().activeWorkspace, Doc, null).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";
+        // this._outer.style.overflow = "hidden";  // bcz: not sure if this is needed.  if it's used, then the doc doesn't highlight when you hover over a docComment
+        (this._outer.style as any).float = node.attrs.float;
+
+        this._dashSpan.style.width = node.attrs.width;
+        this._dashSpan.style.height = node.attrs.height;
+        this._dashSpan.style.position = "absolute";
+        this._dashSpan.style.display = "inline-block";
+        this._dashSpan.onpointerleave = () => {
+            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
+            if (ele) {
+                (ele as HTMLDivElement).style.backgroundColor = "";
+            }
+        };
+        this._dashSpan.onpointerenter = () => {
+            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
+            if (ele) {
+                (ele as HTMLDivElement).style.backgroundColor = "orange";
+            }
+        };
+        const removeDoc = () => {
+            const pos = getPos();
+            const ns = new NodeSelection(view.state.doc.resolve(pos));
+            view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+            return true;
+        };
+        const alias = node.attrs.alias;
+
+        const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
+        DocServer.GetRefField(docid + alias).then(async dashDoc => {
+            if (!(dashDoc instanceof Doc)) {
+                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
+                    if (dashDocBase instanceof Doc) {
+                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
+                        aliasedDoc.layoutKey = "layout";
+                        node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
+                    }
+                });
+            } else {
+                self.doRender(dashDoc, removeDoc, node, view, getPos);
+            }
+        });
+        const self = this;
+        this._dashSpan.onkeydown = function (e: any) {
+            e.stopPropagation();
+            if (e.key === "Tab" || e.key === "Enter") {
+                e.preventDefault();
+            }
+        };
+        this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
+        this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
+        this._outer.appendChild(this._dashSpan);
+        (this as any).dom = this._outer;
+    }
+
+    doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
+        this._dashDoc = dashDoc;
+        const self = this;
+        const dashLayoutDoc = Doc.Layout(dashDoc);
+        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
+
+        if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
+        else {
+            this._reactionDisposer?.();
+            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().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+            }, { fireImmediately: true });
+            const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
+                ReactDOM.unmountComponentAtNode(this._dashSpan);
+
+                ReactDOM.render(<DocumentView
+                    Document={finalLayout}
+                    DataDoc={resolvedDataDoc}
+                    LibraryPath={this._textBox.props.LibraryPath}
+                    fitToBox={BoolCast(dashDoc._fitToBox)}
+                    addDocument={returnFalse}
+                    rootSelected={this._textBox.props.isSelected}
+                    removeDocument={removeDoc}
+                    ScreenToLocalTransform={this.getDocTransform}
+                    addDocTab={this._textBox.props.addDocTab}
+                    pinToPres={returnFalse}
+                    renderDepth={self._textBox.props.renderDepth + 1}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
+                    PanelWidth={finalLayout[WidthSym]}
+                    PanelHeight={finalLayout[HeightSym]}
+                    focus={this.outerFocus}
+                    backgroundColor={returnEmptyString}
+                    parentActive={returnFalse}
+                    whenActiveChanged={returnFalse}
+                    bringToFront={emptyFunction}
+                    dontRegisterView={false}
+                    ContainingCollectionView={this._textBox.props.ContainingCollectionView}
+                    ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
+                    ContentScaling={this.contentScaling}
+                />, this._dashSpan);
+                if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
+                    try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+                        view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                    } catch (e) {
+                        console.log(e);
+                    }
+                }
+            };
+            this._renderDisposer?.();
+            this._renderDisposer = reaction(() => {
+                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
+                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
+                // }
+                const layoutKey = StrCast(finalLayout.layoutKey);
+                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
+                if (finalLayout !== dashDoc && finalKey) {
+                    const finalLayoutField = finalLayout[finalKey];
+                    if (finalLayoutField instanceof ObjectField) {
+                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
+                    }
+                }
+                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
+            },
+                (res) => doReactRender(res.finalLayout, res.resolvedDataDoc),
+                { fireImmediately: true });
+        }
+    }
+    destroy() {
+        ReactDOM.unmountComponentAtNode(this._dashSpan);
+        this._reactionDisposer?.();
+    }
+}
+
+export class DashFieldView {
+    _fieldWrapper: HTMLDivElement; // container for label and value
+    _labelSpan: HTMLSpanElement; // field label
+    _fieldSpan: HTMLSpanElement;  // field value
+    _fieldCheck: HTMLInputElement;
+    _enumerables: HTMLDivElement;  // field value
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBoxDoc: Doc;
+    @observable _dashDoc: Doc | undefined;
+    _fieldKey: string;
+    _options: Doc[] = [];
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._fieldKey = node.attrs.fieldKey;
+        this._textBoxDoc = tbox.props.Document;
+        this._fieldWrapper = document.createElement("p");
+        this._fieldWrapper.style.width = node.attrs.width;
+        this._fieldWrapper.style.height = node.attrs.height;
+        this._fieldWrapper.style.fontWeight = "bold";
+        this._fieldWrapper.style.position = "relative";
+        this._fieldWrapper.style.display = "inline-block";
+
+        const self = this;
+
+        this._enumerables = document.createElement("div");
+        this._enumerables.style.width = "10px";
+        this._enumerables.style.height = "10px";
+        this._enumerables.style.position = "relative";
+        this._enumerables.style.display = "none";
+
+        //Moved
+        this._enumerables.onpointerdown = async (e) => {
+            e.stopPropagation();
+            const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
+            collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
+        };
+        //Moved
+        const updateText = (forceMatch: boolean) => {
+            self._enumerables.style.display = "none";
+            const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
+
+            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
+            // holds the different enumerated values for the field in the titles of its collected documents.
+            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
+            DocServer.GetRefField(self._fieldKey).then(options => {
+                let modText = "";
+                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+                if (modText) {
+                    self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText;
+                    Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []);
+                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
+                else if (self._fieldSpan.innerText.startsWith(":=")) {
+                    self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
+                } else if (self._fieldSpan.innerText.startsWith("=:=")) {
+                    Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3));
+                } else {
+                    self._dashDoc![self._fieldKey] = newText;
+                }
+            });
+        };
+
+        //Moved
+        this._fieldCheck = document.createElement("input");
+        this._fieldCheck.id = Utils.GenerateGuid();
+        this._fieldCheck.type = "checkbox";
+        this._fieldCheck.style.position = "relative";
+        this._fieldCheck.style.display = "none";
+        this._fieldCheck.style.minWidth = "12px";
+        this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
+        this._fieldCheck.onchange = function (e: any) {
+            self._dashDoc![self._fieldKey] = e.target.checked;
+        };
+
+        this._fieldSpan = document.createElement("span");
+        this._fieldSpan.id = Utils.GenerateGuid();
+        this._fieldSpan.contentEditable = "true";
+        this._fieldSpan.style.position = "relative";
+        this._fieldSpan.style.display = "none";
+        this._fieldSpan.style.minWidth = "12px";
+        this._fieldSpan.style.fontSize = "large";
+        this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
+        this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
+        this._fieldSpan.onblur = function (e: any) { updateText(false); };
+
+        // MOVED 
+        const setDashDoc = (doc: Doc) => {
+            self._dashDoc = doc;
+            if (self._options?.length && !self._dashDoc[self._fieldKey]) {
+                self._dashDoc[self._fieldKey] = StrCast(self._options[0].title);
+            }
+            this._labelSpan.innerHTML = `${self._fieldKey}: `;
+            const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
+            this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
+            this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none";
+        };
+
+        //Moved
+        this._fieldSpan.onkeydown = function (e: any) {
+            e.stopPropagation();
+            if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
+                if (window.getSelection) {
+                    const range = document.createRange();
+                    range.selectNodeContents(self._fieldSpan);
+                    window.getSelection()!.removeAllRanges();
+                    window.getSelection()!.addRange(range);
+                }
+                e.preventDefault();
+            }
+            if (e.key === "Enter") {
+                e.preventDefault();
+                e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
+                updateText(true);
+            }
+        };
+
+        this._labelSpan = document.createElement("span");
+        this._labelSpan.style.position = "relative";
+        this._labelSpan.style.fontSize = "small";
+        this._labelSpan.title = "click to see related tags";
+        this._labelSpan.style.fontSize = "x-small";
+        this._labelSpan.onpointerdown = function (e: any) {
+            e.stopPropagation();
+            let container = tbox.props.ContainingCollectionView;
+            while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
+                container = container.props.ContainingCollectionView;
+            }
+            if (container) {
+                const alias = Doc.MakeAlias(container.props.Document);
+                alias.viewType = CollectionViewType.Time;
+                let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
+                if (!list) {
+                    alias.schemaColumns = list = new List<SchemaHeaderField>();
+                }
+                list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb"));
+                list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
+                alias._pivotField = self._fieldKey;
+                tbox.props.addDocTab(alias, "onRight");
+            }
+        };
+        this._labelSpan.innerHTML = `${self._fieldKey}: `;
+        //MOVED
+        if (node.attrs.docid) {
+            DocServer.GetRefField(node.attrs.docid).
+                then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
+        } else {
+            setDashDoc(tbox.props.DataDoc || tbox.dataDoc);
+        }
+
+        //Moved
+        this._reactionDisposer?.();
+        this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes
+            const dashVal = this._dashDoc?.[self._fieldKey];
+            return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal;
+        }, fval => {
+            const boolVal = Cast(fval, "boolean", null);
+            if (boolVal === true || boolVal === false) {
+                this._fieldCheck.checked = boolVal;
+            } else {
+                this._fieldSpan.innerHTML = Field.toString(fval as Field) || "";
+            }
+            this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none";
+            this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none";
+        }, { fireImmediately: true });
+
+        //MOVED IN ORDER
+        this._fieldWrapper.appendChild(this._labelSpan);
+        this._fieldWrapper.appendChild(this._fieldCheck);
+        this._fieldWrapper.appendChild(this._fieldSpan);
+        this._fieldWrapper.appendChild(this._enumerables);
+        (this as any).dom = this._fieldWrapper;
+        //updateText(false);
+    }
+    //MOVED
+    destroy() {
+        this._reactionDisposer?.();
+    }
+    //moved
+    selectNode() { }
+}
+
+export class FootnoteView {
+    innerView: any;
+    outerView: any;
+    node: any;
+    dom: any;
+    getPos: any;
+
+    constructor(node: any, view: any, getPos: any) {
+        // We'll need these later
+        this.node = node;
+        this.outerView = view;
+        this.getPos = getPos;
+
+        // The node's representation in the editor (empty, for now)
+        this.dom = document.createElement("footnote");
+        this.dom.addEventListener("pointerup", this.toggle, true);
+        // These are used when the footnote is selected
+        this.innerView = null;
+    }
+    selectNode() {
+        const attrs = { ...this.node.attrs };
+        attrs.visibility = true;
+        this.dom.classList.add("ProseMirror-selectednode");
+        if (!this.innerView) this.open();
+    }
+
+    deselectNode() {
+        const attrs = { ...this.node.attrs };
+        attrs.visibility = false;
+        this.dom.classList.remove("ProseMirror-selectednode");
+        if (this.innerView) this.close();
+    }
+    open() {
+        // Append a tooltip to the outer node
+        const tooltip = this.dom.appendChild(document.createElement("div"));
+        tooltip.className = "footnote-tooltip";
+        // And put a sub-ProseMirror into that
+        this.innerView = new EditorView(tooltip, {
+            // You can use any node as an editor document
+            state: EditorState.create({
+                doc: this.node,
+                plugins: [keymap(baseKeymap),
+                keymap({
+                    "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
+                    "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
+                    "Mod-b": toggleMark(schema.marks.strong)
+                }),
+                    // new Plugin({
+                    //     view(newView) {
+                    //         // TODO -- make this work with RichTextMenu
+                    //         // return FormattedTextBox.getToolTip(newView);
+                    //     }
+                    // })
+                ],
+
+            }),
+            // This is the magic part
+            dispatchTransaction: this.dispatchInner.bind(this),
+            handleDOMEvents: {
+                pointerdown: ((view: any, e: PointerEvent) => {
+                    // Kludge to prevent issues due to the fact that the whole
+                    // footnote is node-selected (and thus DOM-selected) when
+                    // the parent editor is focused.
+                    e.stopPropagation();
+                    document.addEventListener("pointerup", this.ignore, true);
+                    if (this.outerView.hasFocus()) this.innerView.focus();
+                }) as any
+            }
+
+        });
+        setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
+    }
+
+    ignore = (e: PointerEvent) => {
+        e.stopPropagation();
+        document.removeEventListener("pointerup", this.ignore, true);
+    }
+
+    toggle = () => {
+        if (this.innerView) this.close();
+        else {
+            this.open();
+        }
+    }
+    close() {
+        this.innerView && this.innerView.destroy();
+        this.innerView = null;
+        this.dom.textContent = "";
+    }
+
+    dispatchInner(tr: any) {
+        const { state, transactions } = this.innerView.state.applyTransaction(tr);
+        this.innerView.updateState(state);
+
+        if (!tr.getMeta("fromOutside")) {
+            const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
+            for (const transaction of transactions) {
+                const steps = transaction.steps;
+                for (const step of steps) {
+                    outerTr.step(step.map(offsetMap));
+                }
+            }
+            if (outerTr.docChanged) this.outerView.dispatch(outerTr);
+        }
+    }
+    update(node: any) {
+        if (!node.sameMarkup(this.node)) return false;
+        this.node = node;
+        if (this.innerView) {
+            const state = this.innerView.state;
+            const start = node.content.findDiffStart(state.doc.content);
+            if (start !== null) {
+                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
+                const overlap = start - Math.min(endA, endB);
+                if (overlap > 0) { endA += overlap; endB += overlap; }
+                this.innerView.dispatch(
+                    state.tr
+                        .replace(start, endB, node.slice(start, endA))
+                        .setMeta("fromOutside", true));
+            }
+        }
+        return true;
+    }
+
+    destroy() {
+        if (this.innerView) this.close();
+    }
+
+    stopEvent(event: any) {
+        return this.innerView && this.innerView.dom.contains(event.target);
+    }
+
+    ignoreMutation() { return true; }
+}
+
+export class SummaryView {
+    _collapsed: HTMLElement;
+    _view: any;
+    constructor(node: any, view: any, getPos: any) {
+        this._collapsed = document.createElement("span");
+        this._collapsed.className = this.className(node.attrs.visibility);
+        this._view = view;
+        const js = node.toJSON;
+        node.toJSON = function () {
+            return js.apply(this, arguments);
+        };
+
+        this._collapsed.onpointerdown = (e: any) => {
+            const visible = !node.attrs.visibility;
+            const attrs = { ...node.attrs, visibility: visible };
+            let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
+            if (!visible) { // update summarized text and save in attrs
+                textSelection = this.updateSummarizedText(getPos() + 1);
+                attrs.text = textSelection.content();
+                attrs.textslice = attrs.text.toJSON();
+            }
+            view.dispatch(view.state.tr.
+                setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
+                replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
+                setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+            e.preventDefault();
+            e.stopPropagation();
+            this._collapsed.className = this.className(visible);
+        };
+        (this as any).dom = this._collapsed;
+    }
+    selectNode() { }
+
+    deselectNode() { }
+
+    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+
+    updateSummarizedText(start?: any) {
+        const mtype = this._view.state.schema.marks.summarize;
+        const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
+        let endPos = start;
+
+        const visited = new Set();
+        for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
+            let skip = false;
+            this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+                if (node.isLeaf && !visited.has(node) && !skip) {
+                    if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+                        visited.add(node);
+                        endPos = i + node.nodeSize - 1;
+                    }
+                    else skip = true;
+                }
+            });
+        }
+        return TextSelection.create(this._view.state.doc, start, endPos);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
new file mode 100644
index 000000000..89908d8ee
--- /dev/null
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -0,0 +1,81 @@
+import { TextSelection } from "prosemirror-state";
+import { Fragment, Node, Slice } from "prosemirror-model";
+
+import React = require("react");
+
+interface ISummaryView {
+    node: any;
+    view: any;
+    getPos: any;
+    self: any;
+}
+export class SummaryView extends React.Component<ISummaryView> {
+
+    onPointerDown = (e: any) => {
+        const visible = !this.props.node.attrs.visibility;
+        const attrs = { ...this.props.node.attrs, visibility: visible };
+        let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1);
+        if (!visible) { // update summarized text and save in attrs
+            textSelection = this.updateSummarizedText(this.props.getPos() + 1);
+            attrs.text = textSelection.content();
+            attrs.textslice = attrs.text.toJSON();
+        }
+        this.props.view.dispatch(this.props.view.state.tr.
+            setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
+            replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it
+            setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs
+        e.preventDefault();
+        e.stopPropagation();
+        const _collapsed = document.getElementById('collapse') as HTMLElement;
+        _collapsed.className = this.className(visible);
+    }
+
+    updateSummarizedText(start?: any) {
+        const mtype = this.props.view.state.schema.marks.summarize;
+        const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive;
+        let endPos = start;
+
+        const visited = new Set();
+        for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) {
+            let skip = false;
+            this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+                if (this.props.node.isLeaf && !visited.has(node) && !skip) {
+                    if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+                        visited.add(node);
+                        endPos = i + this.props.node.nodeSize - 1;
+                    }
+                    else skip = true;
+                }
+            });
+        }
+        return TextSelection.create(this.props.view.state.doc, start, endPos);
+    }
+
+    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+
+    selectNode() { }
+
+    deselectNode() { }
+
+    render() {
+        const _view = this.props.node.view;
+        const js = this.props.node.toJSon;
+
+        this.props.node.toJSON = function () {
+            return js.apply(this, arguments);
+        };
+
+        const spanCollapsedClassName = this.className(this.props.node.attrs.visibility);
+
+        return (
+            <span
+                className={spanCollapsedClassName}
+                id='collapse'
+                onPointerDown={this.onPointerDown}
+            >
+
+            </span>
+        );
+
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
new file mode 100644
index 000000000..e2149e9c1
--- /dev/null
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -0,0 +1,372 @@
+@import "../views/globalCssVariables";
+.ProseMirror-menu-dropdown-wrap {
+    display: inline-block;
+    position: relative;
+}
+  
+.ProseMirror-menu-dropdown {
+    vertical-align: 1px;
+    cursor: pointer;
+    position: relative;
+    padding: 0 15px 0 4px;
+    background: white;
+    border-radius: 2px;
+    text-align: left;
+    font-size: 12px;
+    white-space: nowrap;
+    margin-right: 4px;
+
+    &:after {
+        content: "";
+        border-left: 4px solid transparent;
+        border-right: 4px solid transparent;
+        border-top: 4px solid currentColor;
+        opacity: .6;
+        position: absolute;
+        right: 4px;
+        top: calc(50% - 2px);
+    }
+}
+
+.ProseMirror-menu-submenu-wrap {
+    position: relative;
+    margin-right: -4px;
+}
+
+.ProseMirror-menu-dropdown-menu, 
+.ProseMirror-menu-submenu {
+    font-size: 12px;
+    background: white;
+    border: 1px solid rgb(223, 223, 223);
+    min-width: 40px;
+    z-index: 50000;
+    position: absolute;
+    box-sizing: content-box;
+
+    .ProseMirror-menu-dropdown-item {
+        cursor: pointer;
+        z-index: 100000;
+        text-align: left;
+        padding: 3px;
+
+        &:hover {
+            background-color: $light-color-secondary;
+        }
+    }
+}
+
+
+.ProseMirror-menu-submenu-label:after {
+    content: "";
+    border-top: 4px solid transparent;
+    border-bottom: 4px solid transparent;
+    border-left: 4px solid currentColor;
+    opacity: .6;
+    position: absolute;
+    right: 4px;
+    top: calc(50% - 4px);
+}
+  
+  .ProseMirror-icon {
+    display: inline-block;
+    // line-height: .8;
+    // vertical-align: -2px; /* Compensate for padding */
+    // padding: 2px 8px;
+    cursor: pointer;
+
+    &.ProseMirror-menu-disabled {
+        cursor: default;
+    }
+
+    svg {
+        fill:white;
+        height: 1em;
+    }
+
+    span {
+        vertical-align: text-top;
+      }  
+  }
+
+.wrapper {
+    position: absolute;
+    pointer-events: all;
+    display: flex;
+    align-items: center;
+    transform: translateY(-85px);
+
+    height: 35px;
+    background: #323232;
+    border-radius: 6px;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+
+}
+
+.tooltipMenu, .basic-tools {
+    z-index: 20000;
+    pointer-events: all;
+    padding: 3px;
+    padding-bottom: 5px;
+    display: flex;
+    align-items: center;
+
+    .ProseMirror-example-setup-style hr {
+      padding: 2px 10px;
+      border: none;
+      margin: 1em 0;
+    }
+    
+    .ProseMirror-example-setup-style hr:after {
+      content: "";
+      display: block;
+      height: 1px;
+      background-color: silver;
+      line-height: 2px;
+    }
+}
+
+.menuicon {
+    width: 25px;
+    height: 25px;
+    cursor: pointer;
+    text-align: center;
+    line-height: 25px;
+    margin: 0 2px;
+    border-radius: 3px;
+
+    &:hover {
+        background-color: black;
+
+        #link-drag {
+            background-color: black;
+        }
+    }
+
+    &> * {
+        margin-top: 50%;
+        margin-left: 50%;
+        transform: translate(-50%, -50%);
+    }
+
+    svg {
+        fill: white;
+        width: 18px;
+        height: 18px;
+    }
+}
+
+.menuicon-active {
+    width: 25px;
+    height: 25px;
+    cursor: pointer;
+    text-align: center;
+    line-height: 25px;
+    margin: 0 2px;
+    border-radius: 3px;
+
+    &:hover {
+        background-color: black;
+    }
+
+    &> * {
+        margin-top: 50%;
+        margin-left: 50%;
+        transform: translate(-50%, -50%);
+    }
+
+    svg {
+        fill: greenyellow;
+        width: 18px;
+        height: 18px;
+    }
+}
+
+#colorPicker {
+    position: relative;
+
+    svg {
+        width: 18px;
+        height: 18px;
+        // margin-top: 11px;
+    }
+
+    .buttonColor {
+        position: absolute;
+        top: 24px;
+        left: 1px;
+        width: 24px;
+        height: 4px;
+        margin-top: 0;
+    }
+}
+
+#link-drag {
+    background-color: #323232;
+}
+
+.underline svg {
+    margin-top: 13px;
+}
+
+  .font-size-indicator {
+      font-size: 12px;
+      padding-right: 0px;
+  }
+  .summarize{
+      color: white;
+      height: 20px;
+      text-align: center;
+  }
+  
+
+.brush{
+    display: inline-block;
+    width: 1em;
+    height: 1em;
+    stroke-width: 0;
+    stroke: currentColor;
+    fill: currentColor;
+    margin-right: 15px;
+}
+
+.brush-active{
+    display: inline-block;
+    width: 1em;
+    height: 1em;
+    stroke-width: 3;
+    fill: greenyellow;
+    margin-right: 15px;
+}
+
+.dragger-wrapper {
+    color: #eee;
+    height: 22px;
+    padding: 0 5px;
+    box-sizing: content-box;
+    cursor: grab;
+
+    .dragger {
+        width: 18px;
+        height: 100%;
+        display: flex;
+        justify-content: space-evenly;
+    }
+
+    .dragger-line {
+        width: 2px;
+        height: 100%;
+        background-color: black;
+    }
+}
+
+.button-dropdown-wrapper {
+    display: flex;
+    align-content: center;
+
+    &:hover {
+        background-color: black;
+    }
+}
+
+.buttonSettings-dropdown {
+
+    &.ProseMirror-menu-dropdown {
+        width: 10px;
+        height: 25px;
+        margin: 0;
+        padding: 0 2px;
+        background-color: #323232;
+        text-align: center;
+
+        &:after {
+            border-top: 4px solid white;
+            right: 2px;
+        }
+
+        &:hover {
+            background-color: black;
+        }
+    }
+
+    &.ProseMirror-menu-dropdown-menu {
+        min-width: 150px;
+        left: -27px;
+        top: 31px;
+        background-color: #323232;
+        border: 1px solid #4d4d4d;
+        color: $light-color-secondary;
+        // border: none;
+        // border: 1px solid $light-color-secondary;
+        border-radius: 0 6px 6px 6px;
+        padding: 3px;
+        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+
+        .ProseMirror-menu-dropdown-item{
+            cursor: default;
+
+            &:last-child {
+                border-bottom: none;
+            }
+
+            &:hover {
+                background-color: #323232;
+            }
+
+            .button-setting, .button-setting-disabled {
+                padding: 2px;
+                border-radius: 2px;
+            }
+
+            .button-setting:hover {
+                cursor: pointer;
+                background-color: black;
+            }
+
+            .separated-button {
+                border-top: 1px solid $light-color-secondary;
+                padding-top: 6px;
+            }
+
+            input {
+               color: black;
+               border: none;
+               border-radius: 1px;
+               padding: 3px;
+            }
+
+            button {
+               padding: 6px;
+               background-color: #323232;
+               border: 1px solid black;
+               border-radius: 1px;
+
+                &:hover {
+                   background-color: black;
+                }
+            }
+        }
+
+
+    }
+}
+
+.colorPicker-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    margin-top: 3px;
+    margin-left: -3px;
+    width: calc(100% + 6px);
+}
+
+button.colorPicker {
+    width: 20px;
+    height: 20px;
+    border-radius: 15px !important;
+    margin: 3px;
+    border: none !important;
+
+    &.active {
+        border: 2px solid white !important;
+    }
+}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
new file mode 100644
index 000000000..46bf481fb
--- /dev/null
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -0,0 +1,296 @@
+import React = require("react");
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { Doc } from "../../../../new_fields/Doc";
+
+
+const emDOM: DOMOutputSpecArray = ["em", 0];
+const strongDOM: DOMOutputSpecArray = ["strong", 0];
+const codeDOM: DOMOutputSpecArray = ["code", 0];
+
+// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
+export const marks: { [index: string]: MarkSpec } = {
+    // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+    // defaults to the empty string. Rendered and parsed as an `<a>`
+    // element.
+    link: {
+        attrs: {
+            href: {},
+            targetId: { default: "" },
+            linkId: { default: "" },
+            showPreview: { default: true },
+            location: { default: null },
+            title: { default: null },
+            docref: { default: false } // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
+        },
+        inclusive: false,
+        parseDOM: [{
+            tag: "a[href]", getAttrs(dom: any) {
+                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
+            }
+        }],
+        toDOM(node: any) {
+            return node.attrs.docref && node.attrs.title ?
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
+                ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0];
+        }
+    },
+
+
+    // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
+    pFontColor: {
+        attrs: {
+            color: { default: "#000" }
+        },
+        inclusive: true,
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                return { color: dom.getAttribute("color") };
+            }
+        }],
+        toDOM(node: any) {
+            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
+        }
+    },
+
+    marker: {
+        attrs: {
+            highlight: { default: "transparent" }
+        },
+        inclusive: true,
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                return { highlight: dom.getAttribute("backgroundColor") };
+            }
+        }],
+        toDOM(node: any) {
+            return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+        }
+    },
+
+    // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
+    // Has parse rules that also match `<i>` and `font-style: italic`.
+    em: {
+        parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
+        toDOM() { return emDOM; }
+    },
+
+    // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
+    // also match `<b>` and `font-weight: bold`.
+    strong: {
+        parseDOM: [{ tag: "strong" },
+        { tag: "b" },
+        { style: "font-weight" }],
+        toDOM() { return strongDOM; }
+    },
+
+    strikethrough: {
+        parseDOM: [
+            { tag: 'strike' },
+            { style: 'text-decoration=line-through' },
+            { style: 'text-decoration-line=line-through' }
+        ],
+        toDOM: () => ['span', {
+            style: 'text-decoration-line:line-through'
+        }]
+    },
+
+    subscript: {
+        excludes: 'superscript',
+        parseDOM: [
+            { tag: 'sub' },
+            { style: 'vertical-align=sub' }
+        ],
+        toDOM: () => ['sub']
+    },
+
+    superscript: {
+        excludes: 'subscript',
+        parseDOM: [
+            { tag: 'sup' },
+            { style: 'vertical-align=super' }
+        ],
+        toDOM: () => ['sup']
+    },
+
+    mbulletType: {
+        attrs: {
+            bulletType: { default: "decimal" }
+        },
+        toDOM(node: any) {
+            return ['span', {
+                style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
+            }];
+        }
+    },
+
+    metadata: {
+        toDOM() {
+            return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
+        }
+    },
+    metadataKey: {
+        toDOM() {
+            return ['span', { style: 'font-style:italic; ' }];
+        }
+    },
+    metadataVal: {
+        toDOM() {
+            return ['span'];
+        }
+    },
+
+    summarizeInclusive: {
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline") return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            },
+        ],
+        inclusive: true,
+        toDOM() {
+            return ['span', {
+                style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
+            }];
+        }
+    },
+
+    summarize: {
+        inclusive: false,
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline") return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            },
+        ],
+        toDOM() {
+            return ['span', {
+                style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
+            }];
+        }
+    },
+
+    underline: {
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            }
+            // { style: "text-decoration=underline" }
+        ],
+        toDOM: () => ['span', {
+            style: 'text-decoration:underline;text-decoration-style:line'
+        }]
+    },
+
+    search_highlight: {
+        attrs: {
+            selected: { default: false }
+        },
+        parseDOM: [{ style: 'background: yellow' }],
+        toDOM(node: any) {
+            return ['span', {
+                style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
+            }];
+        }
+    },
+
+    // the id of the user who entered the text
+    user_mark: {
+        attrs: {
+            userid: { default: "" },
+            modified: { default: "when?" }, // 1 second intervals since 1970
+        },
+        group: "inline",
+        toDOM(node: any) {
+            const uid = node.attrs.userid.replace(".", "").replace("@", "");
+            const min = Math.round(node.attrs.modified / 12);
+            const hr = Math.round(min / 60);
+            const day = Math.round(hr / 60 / 24);
+            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
+            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
+        }
+    },
+    // the id of the user who entered the text
+    user_tag: {
+        attrs: {
+            userid: { default: "" },
+            modified: { default: "when?" }, // 1 second intervals since 1970
+            tag: { default: "" }
+        },
+        group: "inline",
+        inclusive: false,
+        toDOM(node: any) {
+            const uid = node.attrs.userid.replace(".", "").replace("@", "");
+            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
+        }
+    },
+
+
+    // :: MarkSpec Code font mark. Represented as a `<code>` element.
+    code: {
+        parseDOM: [{ tag: "code" }],
+        toDOM() { return codeDOM; }
+    },
+
+    /* FONTS */
+    pFontFamily: {
+        attrs: {
+            family: { default: "Crimson Text" },
+        },
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                const cstyle = getComputedStyle(dom);
+                if (cstyle.font) {
+                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
+                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
+                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
+                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
+                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
+                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
+                }
+            }
+        }],
+        toDOM: (node) => ['span', {
+            style: `font-family: "${node.attrs.family}";`
+        }]
+    },
+
+    /** FONT SIZES */
+    pFontSize: {
+        attrs: {
+            fontSize: { default: 10 }
+        },
+        parseDOM: [{ style: 'font-size: 10px;' }],
+        toDOM: (node) => ['span', {
+            style: `font-size: ${node.attrs.fontSize}px;`
+        }]
+    },
+};
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
new file mode 100644
index 000000000..e7bcf444a
--- /dev/null
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -0,0 +1,264 @@
+import React = require("react");
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
+import ParagraphNodeSpec from "./ParagraphNodeSpec";
+
+const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
+    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
+
+// :: Object
+// [Specs](#model.NodeSpec) for the nodes defined in this schema.
+export const nodes: { [index: string]: NodeSpec } = {
+    // :: NodeSpec The top level document node.
+    doc: {
+        content: "block+"
+    },
+
+    footnote: {
+        group: "inline",
+        content: "inline*",
+        inline: true,
+        attrs: {
+            visibility: { default: false }
+        },
+        // This makes the view treat the node as a leaf, even though it
+        // technically has content
+        atom: true,
+        toDOM: () => ["footnote", 0],
+        parseDOM: [{ tag: "footnote" }]
+    },
+
+    paragraph: ParagraphNodeSpec,
+
+    // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
+    blockquote: {
+        content: "block+",
+        group: "block",
+        defining: true,
+        parseDOM: [{ tag: "blockquote" }],
+        toDOM() { return blockquoteDOM; }
+    },
+
+    // :: NodeSpec A horizontal rule (`<hr>`).
+    horizontal_rule: {
+        group: "block",
+        parseDOM: [{ tag: "hr" }],
+        toDOM() { return hrDOM; }
+    },
+
+    // :: NodeSpec A heading textblock, with a `level` attribute that
+    // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
+    // `<h6>` elements.
+    heading: {
+        attrs: { level: { default: 1 } },
+        content: "inline*",
+        group: "block",
+        defining: true,
+        parseDOM: [{ tag: "h1", attrs: { level: 1 } },
+        { tag: "h2", attrs: { level: 2 } },
+        { tag: "h3", attrs: { level: 3 } },
+        { tag: "h4", attrs: { level: 4 } },
+        { tag: "h5", attrs: { level: 5 } },
+        { tag: "h6", attrs: { level: 6 } }],
+        toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
+    },
+
+    // :: NodeSpec A code listing. Disallows marks or non-text inline
+    // nodes by default. Represented as a `<pre>` element with a
+    // `<code>` element inside of it.
+    code_block: {
+        content: "text*",
+        marks: "",
+        group: "block",
+        code: true,
+        defining: true,
+        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+        toDOM() { return preDOM; }
+    },
+
+    // :: NodeSpec The text node.
+    text: {
+        group: "inline"
+    },
+
+    dashComment: {
+        attrs: {
+            docid: { default: "" },
+        },
+        inline: true,
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }, "←"];
+        },
+    },
+
+    summary: {
+        inline: true,
+        attrs: {
+            visibility: { default: false },
+            text: { default: undefined },
+            textslice: { default: undefined },
+        },
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }];
+        },
+    },
+
+    // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
+    // `alt`, and `href` attributes. The latter two default to the empty
+    // string.
+    image: {
+        inline: true,
+        attrs: {
+            src: {},
+            agnostic: { default: null },
+            width: { default: 100 },
+            alt: { default: null },
+            title: { default: null },
+            float: { default: "left" },
+            location: { default: "onRight" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "img[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        // TODO if we don't define toDom, dragging the image crashes. Why?
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashDoc: {
+        inline: true,
+        attrs: {
+            width: { default: 200 },
+            height: { default: 100 },
+            title: { default: null },
+            float: { default: "right" },
+            location: { default: "onRight" },
+            hidden: { default: false },
+            fieldKey: { default: "" },
+            docid: { default: "" },
+            alias: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashField: {
+        inline: true,
+        attrs: {
+            fieldKey: { default: "" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    video: {
+        inline: true,
+        attrs: {
+            src: {},
+            width: { default: "100px" },
+            alt: { default: null },
+            title: { default: null }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "video[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["video", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
+    hard_break: {
+        inline: true,
+        group: "inline",
+        selectable: false,
+        parseDOM: [{ tag: "br" }],
+        toDOM() { return brDOM; }
+    },
+
+    ordered_list: {
+        ...orderedList,
+        content: 'list_item+',
+        group: 'block',
+        attrs: {
+            bulletStyle: { default: 0 },
+            mapStyle: { default: "decimal" },
+            setFontSize: { default: undefined },
+            setFontFamily: { default: "inherit" },
+            setFontColor: { default: "inherit" },
+            inheritedFontSize: { default: undefined },
+            visibility: { default: true },
+            indent: { default: undefined }
+        },
+        toDOM(node: Node<any>) {
+            if (node.attrs.mapStyle === "bullet") return ['ul', 0];
+            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
+            const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
+            const ffam = node.attrs.setFontFamily;
+            const color = node.attrs.setFontColor;
+            return node.attrs.visibility ?
+                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
+                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+        }
+    },
+
+    bullet_list: {
+        ...bulletList,
+        content: 'list_item+',
+        group: 'block',
+        // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
+        toDOM(node: Node<any>) {
+            return ['ul', 0];
+        }
+    },
+
+    list_item: {
+        attrs: {
+            bulletStyle: { default: 0 },
+            mapStyle: { default: "decimal" },
+            visibility: { default: true }
+        },
+        ...listItem,
+        content: 'paragraph block*',
+        toDOM(node: any) {
+            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
+            return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
+            //return ["li", { class: `${map}` }, 0];
+        }
+    },
+};
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
new file mode 100644
index 000000000..269423482
--- /dev/null
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -0,0 +1,139 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var prosemirrorInputRules = require('prosemirror-inputrules');
+var prosemirrorTransform = require('prosemirror-transform');
+var prosemirrorModel = require('prosemirror-model');
+
+exports.liftListItem = liftListItem;
+exports.sinkListItem = sinkListItem;
+exports.wrappingInputRule = wrappingInputRule;
+// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
+// Create a command to lift the list item around the selection up into
+// a wrapping list.
+function liftListItem(itemType) {
+    return function (tx, dispatch) {
+        var ref = tx.selection;
+        var $from = ref.$from;
+        var $to = ref.$to;
+        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
+        if (!range) { return false }
+        if (!dispatch) { return true }
+        if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
+        { return liftToOuterList(tx, dispatch, itemType, range) }
+        else // Outer list node
+        { return liftOutOfList(tx, dispatch, range) }
+    }
+}
+
+function liftToOuterList(tr, dispatch, itemType, range) {
+    var end = range.end, endOfList = range.$to.end(range.depth);
+    if (end < endOfList) {
+        // There are siblings after the lifted items, which must become
+        // children of the last item
+        tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList,
+            new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
+        range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
+    }
+    dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView());
+    return true
+}
+
+function liftOutOfList(tr, dispatch, range) {
+    var list = range.parent;
+    // Merge the list items into a single big item
+    for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
+        pos -= list.child(i).nodeSize;
+        tr.delete(pos - 1, pos + 1);
+    }
+    var $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
+    var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
+    var parent = $start.node(-1), indexBefore = $start.index(-1);
+    if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
+        item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false }
+    var start = $start.pos, end = start + item.nodeSize;
+    // Strip off the surrounding list. At the sides where we're not at
+    // the end of the list, the existing list is closed. At sides where
+    // this is the end, it is overwritten to its end.
+    tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
+        new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty)))
+            .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))),
+            atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
+    dispatch(tr.scrollIntoView());
+    return true
+}
+
+// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
+// Create a command to sink the list item around the selection down
+// into an inner list.
+function sinkListItem(itemType) {
+    return function (state, dispatch) {
+        var ref = state.selection;
+        var $from = ref.$from;
+        var $to = ref.$to;
+        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
+        if (!range) { return false }
+        var startIndex = range.startIndex;
+        if (startIndex == 0) { return false }
+        var parent = range.parent, nodeBefore = parent.child(startIndex - 1);
+        if (nodeBefore.type != itemType) { return false; }
+
+        if (dispatch) {
+            var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
+            var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
+            let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))),
+                nestedBefore ? 3 : 1, 0);
+            var before = range.start, after = range.end;
+            dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
+                before, after, slice, 1, true))
+                .scrollIntoView());
+        }
+        return true
+    }
+}
+
+function findWrappingOutside(range, type) {
+    var parent = range.parent;
+    var startIndex = range.startIndex;
+    var endIndex = range.endIndex;
+    var around = parent.contentMatchAt(startIndex).findWrapping(type);
+    if (!around) { return null }
+    var outer = around.length ? around[0] : type;
+    return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
+}
+
+function findWrappingInside(range, type) {
+    var parent = range.parent;
+    var startIndex = range.startIndex;
+    var endIndex = range.endIndex;
+    var inner = parent.child(startIndex);
+    var inside = type.contentMatch.findWrapping(inner.type);
+    if (!inside) { return null }
+    var lastType = inside.length ? inside[inside.length - 1] : type;
+    var innerMatch = lastType.contentMatch;
+    for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); }
+    if (!innerMatch || !innerMatch.validEnd) { return null }
+    return inside
+}
+function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) {
+    if (innerRange === void 0) innerRange = range;
+    let withAttrs = (type) => ({ type: type, attrs: null });
+    var around = findWrappingOutside(range, nodeType);
+    var inner = around && findWrappingInside(innerRange, nodeType);
+    if (!inner) { return null }
+    return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs))
+}
+function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) {
+    return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) {
+        var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
+        var tr = state.tr.delete(start, end);
+        var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs);
+        if (!wrapping) { return null }
+        tr.wrap(range, wrapping);
+        var before = tr.doc.resolve(start - 1).nodeBefore;
+        if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) &&
+            (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
+        return tr
+    })
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts
new file mode 100644
index 000000000..83561073c
--- /dev/null
+++ b/src/client/views/nodes/formattedText/schema_rts.ts
@@ -0,0 +1,26 @@
+import { Schema, Slice } from "prosemirror-model";
+
+import { nodes } from "./nodes_rts";
+import { marks } from "./marks_rts";
+
+
+// :: Schema
+// This schema rougly corresponds to the document schema used by
+// [CommonMark](http://commonmark.org/), minus the list elements,
+// which are defined in the [`prosemirror-schema-list`](#schema-list)
+// module.
+//
+// To reuse elements from this schema, extend or read from its
+// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
+
+export const schema = new Schema({ nodes, marks });
+
+const fromJson = schema.nodeFromJSON;
+
+schema.nodeFromJSON = (json: any) => {
+    const node = fromJson(json);
+    if (json.type === schema.nodes.summary.name) {
+        node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
+    }
+    return node;
+};
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 73ebbb303..69a80e1b4 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -7,7 +7,7 @@ import { observer } from 'mobx-react';
 import { DocServer } from '../client/DocServer';
 import { Docs } from '../client/documents/Documents';
 import { DocumentManager } from '../client/util/DocumentManager';
-import RichTextMenu from '../client/util/RichTextMenu';
+import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
 import { Scripting } from '../client/util/Scripting';
 import { Transform } from '../client/util/Transform';
 import { CollectionView } from '../client/views/collections/CollectionView';
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index c9960e783..c475d0d73 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -4,11 +4,11 @@ import { Fragment, Mark, Node } from "prosemirror-model";
 import { sinkListItem } from "prosemirror-schema-list";
 import { Utils } from "../Utils";
 import { Docs } from "../client/documents/Documents";
-import { schema } from "../client/util/schema_rts";
+import { schema } from "../client/views/nodes/formattedText/schema_rts";
 import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
 import { DocServer } from "../client/DocServer";
 import { Networking } from "../client/Network";
-import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox";
 import { Doc, Opt } from "./Doc";
 import { Id } from "./FieldSymbols";
 import { RichTextField } from "./RichTextField";
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 1d41c3570..08dc21460 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -17,7 +17,7 @@ import { CollectionViewType } from "../../../client/views/collections/Collection
 import { makeTemplate } from "../../../client/util/DropConverter";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { PrefetchProxy } from "../../../new_fields/Proxy";
-import { FormattedTextBox } from "../../../client/views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox";
 import { MainView } from "../../../client/views/MainView";
 import { DocumentType } from "../../../client/documents/DocumentTypes";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-- 
cgit v1.2.3-70-g09d2

