From b8c0dc61d535caeb2142678dffea2a841776e3a8 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 13 Mar 2019 16:39:45 -0400
Subject: moved DocumentContents into DocumentContentsView file.

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

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
new file mode 100644
index 000000000..55b4938a0
--- /dev/null
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -0,0 +1,35 @@
+import { Document } from "../../../fields/Document";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { ImageBox } from "../nodes/ImageBox";
+import { VideoBox } from "../nodes/VideoBox";
+import { AudioBox } from "../nodes/AudioBox";
+import { KeyValueBox } from "./KeyValueBox"
+import { WebBox } from "../nodes/WebBox";
+import { PDFBox } from "../nodes/PDFBox";
+import "./DocumentView.scss";
+import React = require("react");
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
+interface JsxBindings {
+    Document: Document;
+    layout: string;
+    [prop: string]: any;
+}
+
+export class DocumentContentsView extends React.PureComponent<JsxBindings> {
+    render() {
+        return <JsxParser
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
+            bindings={this.props}
+            jsx={this.props.layout}
+            showWarnings={true}
+            onError={(test: any) => { console.log(test) }}
+        />
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 1bf3a5a0108704a0204cedcbad6e6d52d19cf982 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 17 Mar 2019 22:04:03 -0400
Subject: moved DocumentContents to DocumentContentsView

---
 src/client/views/InkingControl.tsx                 |  8 +++
 .../views/collections/CollectionFreeFormView.tsx   | 34 ++----------
 src/client/views/collections/MarqueeView.tsx       |  2 +-
 src/client/views/collections/PreviewCursor.tsx     | 10 ++--
 src/client/views/nodes/DocumentContentsView.tsx    | 60 +++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            | 55 ++------------------
 6 files changed, 62 insertions(+), 107 deletions(-)

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

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index fb75ef2a5..6616f68d8 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -9,6 +9,8 @@ import "./InkingCanvas.scss"
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
+import { SelectionManager } from "../util/SelectionManager";
+import { KeyStore } from "../../fields/KeyStore";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -34,6 +36,12 @@ export class InkingControl extends React.Component {
     @action
     switchColor = (color: ColorResult): void => {
         this._selectedColor = color.hex;
+        if (SelectionManager.SelectedDocuments().length == 1) {
+            var sdoc = SelectionManager.SelectedDocuments()[0];
+            if (sdoc.props.ContainingCollectionView && sdoc.props.ContainingCollectionView) {
+                sdoc.props.Document.SetText(KeyStore.BackgroundColor, color.hex);
+            }
+        }
     }
 
     @action
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 988c9941f..cca57912c 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -8,28 +8,16 @@ import { TextField } from "../../../fields/TextField";
 import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { CollectionView } from "../collections/CollectionView";
 import { InkingCanvas } from "../InkingCanvas";
-import { AudioBox } from "../nodes/AudioBox";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView, DocumentViewProps, DocumentContents } from "../nodes/DocumentView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { KeyValueBox } from "../nodes/KeyValueBox";
-import { PDFBox } from "../nodes/PDFBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { WebBox } from "../nodes/WebBox";
+import { DocumentContentsView } from "../nodes/DocumentContentsView";
+import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import "./CollectionFreeFormView.scss";
 import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
 import { CollectionViewBase } from "./CollectionViewBase";
 import { MarqueeView } from "./MarqueeView";
 import { PreviewCursor } from "./PreviewCursor";
 import React = require("react");
-const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
 
 @observer
 export class CollectionFreeFormView extends CollectionViewBase {
@@ -283,28 +271,14 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @computed
     get backgroundView() {
         return !this.backgroundLayout ? (null) :
-            (<DocumentContents {...this.getDocumentViewProps(this.props.Document)}
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
                 layoutKey={KeyStore.BackgroundLayout} isSelected={() => false} select={() => { }} />);
-        // (<JsxParser
-        //     components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-        //     bindings={this.props.bindings}
-        //     jsx={this.backgroundLayout}
-        //     showWarnings={true}
-        //     onError={(test: any) => console.log(test)}
-        // />);
     }
     @computed
     get overlayView() {
         return !this.overlayLayout ? (null) :
-            (<DocumentContents {...this.getDocumentViewProps(this.props.Document)}
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
                 layoutKey={KeyStore.OverlayLayout} isSelected={() => false} select={() => { }} />);
-        // (<JsxParser
-        //     components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-        //     bindings={this.props.bindings}
-        //     jsx={this.overlayLayout}
-        //     showWarnings={true}
-        //     onError={(test: any) => console.log(test)}
-        // />);
     }
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
index 65aaa837f..f5c83a934 100644
--- a/src/client/views/collections/MarqueeView.tsx
+++ b/src/client/views/collections/MarqueeView.tsx
@@ -121,7 +121,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
         let centerShiftY = 0 - (selRect.top + selRect.height / 2);
         let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-        if (ink && ink != FieldWaiting) {
+        if (ink && ink != FieldWaiting && ink.Data) {
             let idata = new Map();
             ink.Data.forEach((value: StrokeData, key: string, map: any) => {
                 let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
index a1411250a..cbcfa568d 100644
--- a/src/client/views/collections/PreviewCursor.tsx
+++ b/src/client/views/collections/PreviewCursor.tsx
@@ -1,16 +1,12 @@
-import { trace } from "mobx";
-import "./PreviewCursor.scss";
-import React = require("react");
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
-import { FieldWaiting, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
+import { Opt } from "../../../fields/Field";
 import { Documents } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./PreviewCursor.scss";
+import React = require("react");
 
 
 export interface PreviewCursorProps {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 55b4938a0..ce72ab64b 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,33 +1,55 @@
-import { Document } from "../../../fields/Document";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { FieldWaiting } from "../../../fields/Field";
+import { Key } from "../../../fields/Key";
+import { KeyStore } from "../../../fields/KeyStore";
+import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { AudioBox } from "../nodes/AudioBox";
-import { KeyValueBox } from "./KeyValueBox"
-import { WebBox } from "../nodes/WebBox";
-import { PDFBox } from "../nodes/PDFBox";
+import { CollectionView } from "../collections/CollectionView";
+import { AudioBox } from "./AudioBox";
+import { DocumentViewProps, JsxBindings } from "./DocumentView";
 import "./DocumentView.scss";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { ImageBox } from "./ImageBox";
+import { KeyValueBox } from "./KeyValueBox";
+import { PDFBox } from "./PDFBox";
+import { VideoBox } from "./VideoBox";
+import { WebBox } from "./WebBox";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
-interface JsxBindings {
-    Document: Document;
-    layout: string;
-    [prop: string]: any;
-}
 
-export class DocumentContentsView extends React.PureComponent<JsxBindings> {
+@observer
+export class DocumentContentsView extends React.Component<DocumentViewProps & {
+    isSelected: () => boolean,
+    select: (ctrl: boolean) => void,
+    layoutKey: Key
+}> {
+    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
+    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
+    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
+
+    CreateBindings(): JsxBindings {
+        let bindings: JsxBindings = { ...this.props, };
+        for (const key of this.layoutKeys) {
+            bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
+        }
+        for (const key of this.layoutFields) {
+            let field = this.props.Document.Get(key);
+            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+        }
+        return bindings;
+    }
+
     render() {
         return <JsxParser
             components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-            bindings={this.props}
-            jsx={this.props.layout}
+            bindings={this.CreateBindings()}
+            jsx={this.layout}
             showWarnings={true}
             onError={(test: any) => { console.log(test) }}
         />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3e691ee6d..b36d47b8b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,12 @@
 import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting, Opt } from "../../../fields/Field";
+import { Field, Opt } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { TextField } from "../../../fields/TextField";
+import { Utils } from "../../../Utils";
 import { Documents } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
@@ -14,22 +15,9 @@ import { Transform } from "../../util/Transform";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionView, CollectionViewType } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
+import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-import { DocumentContentsView } from "./DocumentContentsView";
-import { Utils } from "../../../Utils";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { ImageBox } from "./ImageBox";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { WebBox } from "./WebBox";
-import { KeyValueBox } from "./KeyValueBox";
-import { PDFBox } from "./PDFBox";
-import { VideoBox } from "./VideoBox";
-import { AudioBox } from "./AudioBox";
-const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 
 export interface DocumentViewProps {
@@ -87,7 +75,7 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
     return args;
 }
 
-interface JsxBindings {
+export interface JsxBindings {
     Document: Document;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
@@ -96,40 +84,7 @@ interface JsxBindings {
     [prop: string]: any;
 }
 
-@observer
-export class DocumentContents extends React.Component<DocumentViewProps & {
-    isSelected: () => boolean,
-    select: (ctrl: boolean) => void,
-    layoutKey: Key
-}> {
-    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
-    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
-    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
-    CreateBindings(): JsxBindings {
-        let bindings: JsxBindings = {
-            ...this.props,
-        };
-        for (const key of this.layoutKeys) {
-            bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
-        }
-        for (const key of this.layoutFields) {
-            let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
-        }
-        return bindings;
-    }
 
-    render() {
-        return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-            bindings={this.CreateBindings()}
-            jsx={this.layout}
-            showWarnings={true}
-            onError={(test: any) => { console.log(test) }}
-        />
-    }
-}
 
 @observer
 export class DocumentView extends React.Component<DocumentViewProps> {
@@ -377,7 +332,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                 onDrop={this.onDrop}
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown} >
-                <DocumentContents {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
+                <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
             </div>
         )
     }
-- 
cgit v1.2.3-70-g09d2


From a16e6592caafb601b59c3d9f7609e8c1af231eba Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 20 Mar 2019 18:00:39 -0400
Subject: initial

---
 package.json                                       |   2 +
 src/client/documents/Documents.ts                  |  14 +-
 src/client/northstar/core/BaseObject.ts            |  12 ++
 .../northstar/core/attribute/AttributeModel.ts     | 111 ++++++++++
 .../core/attribute/AttributeTransformationModel.ts |  47 ++++
 .../core/attribute/CalculatedAttributeModel.ts     |  42 ++++
 .../northstar/core/brusher/BrushLinkModel.ts       |  40 ++++
 .../northstar/core/brusher/IBaseBrushable.ts       |  13 ++
 src/client/northstar/core/brusher/IBaseBrusher.ts  |  11 +
 src/client/northstar/core/filter/FilterModel.ts    |  81 +++++++
 src/client/northstar/core/filter/FilterOperand.ts  |   5 +
 src/client/northstar/core/filter/FilterType.ts     |   6 +
 .../northstar/core/filter/IBaseFilterConsumer.ts   |  10 +
 .../northstar/core/filter/IBaseFilterProvider.ts   |   8 +
 .../northstar/core/filter/ValueComparision.ts      |  74 +++++++
 src/client/northstar/model/ModelExtensions.ts      |  48 +++++
 src/client/northstar/model/ModelHelpers.ts         | 215 +++++++++++++++++++
 .../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    |  36 ++++
 .../model/binRanges/VisualBinRangeHelper.ts        |  71 +++++++
 src/client/northstar/operations/BaseOperation.ts   | 174 +++++++++++++++
 .../northstar/operations/HistogramOperation.ts     | 111 ++++++++++
 src/client/northstar/utils/ArrayUtil.ts            |  90 ++++++++
 src/client/northstar/utils/Extensions.ts           |  20 ++
 src/client/northstar/utils/GeometryUtil.ts         | 129 +++++++++++
 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/MathUtil.ts             | 236 +++++++++++++++++++++
 src/client/northstar/utils/PartialClass.ts         |   7 +
 src/client/northstar/utils/Utils.ts                |  75 +++++++
 src/client/views/Main.tsx                          |  47 +++-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/FieldView.tsx               |   7 +-
 src/client/views/nodes/HistogramBox.scss           |   8 +
 src/client/views/nodes/HistogramBox.tsx            |  67 ++++++
 src/fields/KeyStore.ts                             |   1 +
 40 files changed, 2250 insertions(+), 14 deletions(-)
 create mode 100644 src/client/northstar/core/BaseObject.ts
 create mode 100644 src/client/northstar/core/attribute/AttributeModel.ts
 create mode 100644 src/client/northstar/core/attribute/AttributeTransformationModel.ts
 create mode 100644 src/client/northstar/core/attribute/CalculatedAttributeModel.ts
 create mode 100644 src/client/northstar/core/brusher/BrushLinkModel.ts
 create mode 100644 src/client/northstar/core/brusher/IBaseBrushable.ts
 create mode 100644 src/client/northstar/core/brusher/IBaseBrusher.ts
 create mode 100644 src/client/northstar/core/filter/FilterModel.ts
 create mode 100644 src/client/northstar/core/filter/FilterOperand.ts
 create mode 100644 src/client/northstar/core/filter/FilterType.ts
 create mode 100644 src/client/northstar/core/filter/IBaseFilterConsumer.ts
 create mode 100644 src/client/northstar/core/filter/IBaseFilterProvider.ts
 create mode 100644 src/client/northstar/core/filter/ValueComparision.ts
 create mode 100644 src/client/northstar/model/ModelExtensions.ts
 create mode 100644 src/client/northstar/model/ModelHelpers.ts
 create mode 100644 src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/NominalVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/VisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
 create mode 100644 src/client/northstar/operations/BaseOperation.ts
 create mode 100644 src/client/northstar/operations/HistogramOperation.ts
 create mode 100644 src/client/northstar/utils/ArrayUtil.ts
 create mode 100644 src/client/northstar/utils/Extensions.ts
 create mode 100644 src/client/northstar/utils/GeometryUtil.ts
 create mode 100644 src/client/northstar/utils/IDisposable.ts
 create mode 100644 src/client/northstar/utils/IEquatable.ts
 create mode 100644 src/client/northstar/utils/KeyCodes.ts
 create mode 100644 src/client/northstar/utils/MathUtil.ts
 create mode 100644 src/client/northstar/utils/PartialClass.ts
 create mode 100644 src/client/northstar/utils/Utils.ts
 create mode 100644 src/client/views/nodes/HistogramBox.scss
 create mode 100644 src/client/views/nodes/HistogramBox.tsx

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

diff --git a/package.json b/package.json
index c87e31bc4..33333a402 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
     "@types/connect-flash": "0.0.34",
     "@types/cookie-parser": "^1.4.1",
     "@types/cookie-session": "^2.0.36",
+    "@types/d3-format": "^1.3.1",
     "@types/express": "^4.16.1",
     "@types/express-flash": "0.0.0",
     "@types/express-session": "^1.15.12",
@@ -93,6 +94,7 @@
     "cookie-parser": "^1.4.4",
     "cookie-session": "^2.0.0-beta.3",
     "crypto-browserify": "^3.11.0",
+    "d3-format": "^1.3.2",
     "express": "^4.16.4",
     "express-flash": "0.0.2",
     "express-session": "^1.15.6",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4ed1fc3ed..b1e1f8c7b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -22,6 +22,7 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
 import { VideoBox } from "../views/nodes/VideoBox";
 import { WebBox } from "../views/nodes/WebBox";
+import { HistogramBox } from "../views/nodes/HistogramBox";
 
 export interface DocumentOptions {
     x?: number;
@@ -44,6 +45,7 @@ export interface DocumentOptions {
 
 export namespace Documents {
     let textProto: Document;
+    let histoProto: Document;
     let imageProto: Document;
     let webProto: Document;
     let collProto: Document;
@@ -52,6 +54,7 @@ export namespace Documents {
     let audioProto: Document;
     let pdfProto: Document;
     const textProtoId = "textProto";
+    const histoProtoId = "histoProto";
     const pdfProtoId = "pdfProto";
     const imageProtoId = "imageProto";
     const webProtoId = "webProto";
@@ -62,9 +65,10 @@ export namespace Documents {
 
     export function initProtos(callback: () => void) {
         Server.GetFields([collProtoId, textProtoId, imageProtoId], (fields) => {
+            textProto = fields[textProtoId] as Document;
+            histoProto = fields[histoProtoId] as Document;
             collProto = fields[collProtoId] as Document;
             imageProto = fields[imageProtoId] as Document;
-            textProto = fields[textProtoId] as Document;
             webProto = fields[webProtoId] as Document;
             kvpProto = fields[kvpProtoId] as Document;
             callback();
@@ -114,6 +118,11 @@ export namespace Documents {
         }
         return imageProto;
     }
+    function GetHistogramPrototype(): Document {
+        return histoProto ? histoProto :
+            histoProto = setupPrototypeOptions(textProtoId, "HISTO PROTOTYPE", HistogramBox.LayoutString(),
+                { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+    }
     function GetTextPrototype(): Document {
         return textProto ? textProto :
             textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
@@ -176,6 +185,9 @@ export namespace Documents {
         return assignToDelegate(SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]), options);
     }
 
+    export function HistogramDocument(options: DocumentOptions = {}) {
+        return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, ["", TextField]).MakeDelegate(), options);
+    }
     export function TextDocument(options: DocumentOptions = {}) {
         return assignToDelegate(SetInstanceOptions(GetTextPrototype(), options, ["", TextField]).MakeDelegate(), options);
     }
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
new file mode 100644
index 000000000..e9e766e31
--- /dev/null
+++ b/src/client/northstar/core/BaseObject.ts
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 000000000..124a5b45a
--- /dev/null
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -0,0 +1,111 @@
+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
new file mode 100644
index 000000000..cc5aa7154
--- /dev/null
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -0,0 +1,47 @@
+;
+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
new file mode 100644
index 000000000..ab96c794d
--- /dev/null
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -0,0 +1,42 @@
+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/BrushLinkModel.ts b/src/client/northstar/core/brusher/BrushLinkModel.ts
new file mode 100644
index 000000000..e3ac43367
--- /dev/null
+++ b/src/client/northstar/core/brusher/BrushLinkModel.ts
@@ -0,0 +1,40 @@
+import { IBaseBrushable } from '../brusher/IBaseBrushable'
+import { IBaseBrusher } from '../brusher/IBaseBrusher'
+import { Utils } from '../../utils/Utils'
+import { IEquatable } from '../../utils/IEquatable';
+
+export class BrushLinkModel<T> implements IEquatable {
+
+    public From: IBaseBrusher<T>;
+
+    public To: IBaseBrushable<T>;
+
+    public Color: number = 0;
+
+    constructor(from: IBaseBrusher<T>, to: IBaseBrushable<T>) {
+        this.From = from;
+        this.To = to;
+    }
+
+    public static overlaps(start: number, end: number, otherstart: number, otherend: number): boolean {
+        if (start > otherend || otherstart > end)
+            return false;
+        return true;
+    }
+    public static Connected<T>(from: IBaseBrusher<T>, to: IBaseBrushable<T>): boolean {
+        var connected = (Math.abs(from.Position.x + from.Size.x - to.Position.x) <= 60 &&
+            this.overlaps(from.Position.y, from.Position.y + from.Size.y, to.Position.y, to.Position.y + to.Size.y)
+        ) ||
+            (Math.abs(to.Position.x + to.Size.x - from.Position.x) <= 60 &&
+                this.overlaps(to.Position.y, to.Position.y + to.Size.y, from.Position.y, from.Position.y + from.Size.y)
+            );
+        return connected;
+    }
+
+    public Equals(other: Object): boolean {
+        if (!Utils.EqualityHelper(this, other)) return false;
+        if (!this.From.Equals((other as BrushLinkModel<T>).From)) return false;
+        if (!this.To.Equals((other as BrushLinkModel<T>).To)) return false;
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
new file mode 100644
index 000000000..07d4e7580
--- /dev/null
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -0,0 +1,13 @@
+import { BrushLinkModel } from '../brusher/BrushLinkModel'
+import { PIXIPoint } from '../../utils/MathUtil'
+import { IEquatable } from '../../utils/IEquatable';
+
+export interface IBaseBrushable<T> extends IEquatable {
+    BrusherModels: Array<BrushLinkModel<T>>;
+    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
new file mode 100644
index 000000000..d7ae65464
--- /dev/null
+++ b/src/client/northstar/core/brusher/IBaseBrusher.ts
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 000000000..3c4cfc4a7
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -0,0 +1,81 @@
+import { ValueComparison } from "./ValueComparision";
+import { Utils } from "../../utils/Utils";
+
+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 as FilterModel).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 {
+        let ret = "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
+        return ret;
+    }
+
+    public static And(filters: string[]): string {
+        let ret = filters.filter(f => f !== "").join(" && ");
+        return ret;
+    }
+
+    // public static GetFilterModelsRecursive(filterGraphNode: GraphNode<BaseOperationViewModel, FilterLinkViewModel>,
+    //     visitedFilterProviders: Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>, filterModels: FilterModel[], isFirst: boolean): string {
+    //     let ret = "";
+    //     if (Utils.isBaseFilterProvider(filterGraphNode.Data)) {
+    //         visitedFilterProviders.add(filterGraphNode);
+    //         let filtered = filterGraphNode.Data.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0);
+    //         if (!isFirst && filtered.length > 0) {
+    //             filterModels.push(...filtered);
+    //             ret = "(" + filterGraphNode.Data.FilterModels.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+    //         }
+    //     }
+    //     if (Utils.isBaseFilterConsumer(filterGraphNode.Data) && filterGraphNode.Links != null) {
+    //         let children = new Array<string>();
+    //         let linkedGraphNodes = filterGraphNode.Links.get(LinkType.Filter);
+    //         if (linkedGraphNodes != null) {
+    //             for (let i = 0; i < linkedGraphNodes.length; i++) {
+    //                 let linkVm = linkedGraphNodes[i].Data;
+    //                 let linkedGraphNode = linkedGraphNodes[i].Target;
+    //                 if (!visitedFilterProviders.has(linkedGraphNode)) {
+    //                     let child = FilterModel.GetFilterModelsRecursive(linkedGraphNode, visitedFilterProviders, filterModels, false);
+    //                     if (child !== "") {
+    //                         if (linkVm.IsInverted) {
+    //                             child = "! " + child;
+    //                         }
+    //                         children.push(child);
+    //                     }
+    //                 }
+    //             }
+    //         }
+
+    //         let childrenJoined = children.join(filterGraphNode.Data.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
new file mode 100644
index 000000000..2e8e8d6a0
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterOperand.ts
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 000000000..9adbc087f
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterType.ts
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 000000000..e687acb8a
--- /dev/null
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -0,0 +1,10 @@
+import { FilterOperand } from '../filter/FilterOperand'
+import { IEquatable } from '../../utils/IEquatable'
+
+export interface IBaseFilterConsumer extends IEquatable {
+    FilterOperand: FilterOperand;
+}
+
+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
new file mode 100644
index 000000000..d082bfe12
--- /dev/null
+++ b/src/client/northstar/core/filter/IBaseFilterProvider.ts
@@ -0,0 +1,8 @@
+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
new file mode 100644
index 000000000..1e729d06e
--- /dev/null
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -0,0 +1,74 @@
+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/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
new file mode 100644
index 000000000..9fcba7f1c
--- /dev/null
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -0,0 +1,48 @@
+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 (<any>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
new file mode 100644
index 000000000..e1241b3ef
--- /dev/null
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -0,0 +1,215 @@
+
+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 { Main } from "../../views/Main";
+
+export class ModelHelpers {
+
+    public static CreateAggregateKey(atm: AttributeTransformationModel, histogramResult: HistogramResult,
+        brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
+        {
+            if (aggParameters == undefined) {
+                aggParameters = ModelHelpers.GetAggregateParameter(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 ArrayUtil.IndexOfWithEqual(histogramResult.aggregateParameters!, aggParameters);
+    }
+
+    public static GetAggregateParameter(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 = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+            aggParam = avg;
+        }
+        else if (atm.AggregateFunction === AggregateFunction.Count) {
+            var cnt = new CountAggregateParameters();
+            cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+            cnt.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+            aggParam = cnt;
+        }
+        else if (atm.AggregateFunction === AggregateFunction.Sum) {
+            var sum = new SumAggregateParameters();
+            sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+            sum.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+            aggParam = sum;
+        }
+        return aggParam;
+    }
+
+    public static GetAggregateParametersWithMargins(atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
+        var aggregateParameters = new Array<AggregateParameters>();
+        atms.forEach(agg => {
+            var aggParams = ModelHelpers.GetAggregateParameter(agg);
+            if (aggParams) {
+                aggregateParameters.push(aggParams);
+
+                var margin = new MarginAggregateParameters();
+                margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
+                margin.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+                margin.aggregateFunction = agg.AggregateFunction;
+                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 as BackendAttributeModel).Id
+                });
+        }
+        else if (am instanceof CodeAttributeModel) {
+            return new AttributeCodeParameters(
+                {
+                    rawName: am.CodeName,
+                    visualizationHints: am.VisualizationHints,
+                    code: (am as CodeAttributeModel).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
new file mode 100644
index 000000000..995bf4e0b
--- /dev/null
+++ b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
@@ -0,0 +1,52 @@
+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
new file mode 100644
index 000000000..9313fb1a7
--- /dev/null
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -0,0 +1,105 @@
+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
new file mode 100644
index 000000000..407ff3ea6
--- /dev/null
+++ b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
@@ -0,0 +1,52 @@
+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
new file mode 100644
index 000000000..80886416b
--- /dev/null
+++ b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
@@ -0,0 +1,90 @@
+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[] {
+        let 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 {
+        let extent = QuantitativeVisualBinRange.getExtent(dataMinValue, dataMaxValue, targetBinNumber, isIntegerRange);
+        let 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;
+        }
+        let span = dataMax - dataMin;
+
+        let step = Math.pow(10, Math.floor(Math.log10(span / m)));
+        let 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);
+        }
+        let ret: number[] = new Array<number>(3);
+        let minDivStep = Math.floor(dataMin / step);
+        let 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
new file mode 100644
index 000000000..f53008f9a
--- /dev/null
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -0,0 +1,36 @@
+import { BinLabel } from '../../model/idea/idea'
+
+export abstract class VisualBinRange {
+
+    constructor() {
+
+    }
+
+    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
new file mode 100644
index 000000000..9eae39800
--- /dev/null
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -0,0 +1,71 @@
+import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult } 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 { Settings } 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 as NominalBinRange);
+        }
+        else if (dataBinRange instanceof QuantitativeBinRange) {
+            return new QuantitativeVisualBinRange(dataBinRange as QuantitativeBinRange);
+        }
+        else if (dataBinRange instanceof AlphabeticBinRange) {
+            return new AlphabeticVisualBinRange(dataBinRange as AlphabeticBinRange);
+        }
+        else if (dataBinRange instanceof DateTimeBinRange) {
+            return new DateTimeVisualBinRange(dataBinRange as DateTimeBinRange);
+        }
+        throw new Exception()
+    }
+
+    public static GetVisualBinRange(dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
+
+        if (!(dataBinRange instanceof AggregateBinRange)) {
+            return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange);
+        }
+        else {
+            var aggregateKey = ModelHelpers.CreateAggregateKey(attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
+            var minValue = Number.MAX_VALUE;
+            var maxValue = Number.MIN_VALUE;
+            for (var b = 0; b < histoResult.brushes!.length; b++) {
+                var brush = histoResult.brushes![b];
+                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 as QuantitativeVisualBinRange).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/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
new file mode 100644
index 000000000..4c0303a48
--- /dev/null
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -0,0 +1,174 @@
+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 public Result: Result | undefined;
+    @observable public ComputationStarted: boolean = false;
+    public OperationReference: OperationReference | undefined = undefined;
+
+    private static _nextId = 0;
+    public RequestSalt: string = "";
+    public Id: number;
+
+    constructor() {
+        this.Id = BaseOperation._nextId++;
+    }
+
+    @computed
+    public get FilterString(): string {
+
+        // if (this.OverridingFilters.length > 0) {
+        //     return "(" + this.OverridingFilters.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+        // }
+        // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
+        // let sliceModel = this.TypedViewModel.IncomingSliceModel;
+        // if (sliceModel != null && sliceModel.Source != null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
+        //     let filterModels = sliceModel.Source.FilterModels.map(f => f);
+        //     return FilterModel.GetFilterModelsRecursive(rdg.get(sliceModel.Source), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
+        // }
+
+        // if (rdg.has(this.TypedViewModel)) {
+        //     let filterModels = [];
+        //     return FilterModel.GetFilterModelsRecursive(rdg.get(this.TypedViewModel), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, true)
+        // }
+        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());
+                }
+            }
+
+            let operationParameters = this.CreateOperationParameters();
+            this.Result = undefined;
+            this.Error = "";
+            let salt = Math.random().toString();
+            this.RequestSalt = salt;
+
+            if (!operationParameters) {
+                this.ComputationStarted = false;
+                return;
+            }
+
+            this.ComputationStarted = true;
+            //let start = performance.now();
+            let promise = Gateway.Instance.StartOperation(operationParameters.toJSON());
+            promise.catch(err => {
+                action(() => {
+                    this.Error = err;
+                    console.error(err);
+                });
+            });
+            let operationReference = await promise;
+
+
+            if (operationReference) {
+                this.OperationReference = operationReference;
+
+                let resultParameters = new ResultParameters();
+                resultParameters.operationReference = operationReference;
+
+                let pollPromise = new PollPromise(salt, operationReference);
+                BaseOperation._currentOperations.set(this.Id, pollPromise);
+
+                pollPromise.Start(async () => {
+                    let result = await Gateway.Instance.GetResult(resultParameters.toJSON());
+                    if (result instanceof ErrorResult) {
+                        throw new Error((result as ErrorResult).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
new file mode 100644
index 000000000..a4f5cac70
--- /dev/null
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -0,0 +1,111 @@
+import { reaction, computed, action } from "mobx";
+import { Attribute, DataType, QuantitativeBinRange, HistogramOperationParameters, AggregateParameters, AggregateFunction, AverageAggregateParameters } from "../model/idea/idea";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttributeModel";
+import { ModelHelpers } from "../model/ModelHelpers";
+import { SETTINGS_X_BINS, SETTINGS_Y_BINS, SETTINGS_SAMPLE_SIZE } from "../model/binRanges/VisualBinRangeHelper";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { Main } from "../../views/Main";
+import { BaseOperation } from "./BaseOperation";
+
+
+export class HistogramOperation extends BaseOperation {
+    public X: AttributeTransformationModel;
+    public Y: AttributeTransformationModel;
+    public V: AttributeTransformationModel;
+    constructor(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel) {
+        super();
+        this.X = x;
+        this.Y = y;
+        this.V = v;
+        reaction(() => this.createOperationParamsCache, () => this.Update());
+    }
+
+    @computed.struct
+    public get BrushString() {
+        return [];
+        // let brushes = [];
+        // this.TypedViewModel.BrusherModels.map(brushLinkModel => {
+        //     if (instanceOfIBaseFilterProvider(brushLinkModel.From) && brushLinkModel.From.FilterModels.some && brushLinkModel.From instanceof BaseOperationViewModel) {
+        //         let brushFilterModels = [];
+        //         let gnode = MainManager.Instance.MainViewModel.FilterDependencyGraph.has(brushLinkModel.From) ?
+        //             MainManager.Instance.MainViewModel.FilterDependencyGraph.get(brushLinkModel.From) :
+        //             new GraphNode<BaseOperationViewModel, FilterLinkViewModel>(brushLinkModel.From);
+        //         let brush = FilterModel.GetFilterModelsRecursive(gnode, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), brushFilterModels, false);
+        //         brushes.push(brush);
+        //     }
+        // });
+        // return brushes;
+    }
+
+
+    @computed.struct
+    public get SelectionString() {
+        return "";
+        // let filterModels = new Array<FilterModel>();
+        // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
+        // let graphNode: GraphNode<BaseOperationViewModel, FilterLinkViewModel>;
+        // if (rdg.has(this.TypedViewModel)) {
+        //     graphNode = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph.get(this.TypedViewModel);
+        // }
+        // else {
+        //     graphNode = new GraphNode<BaseOperationViewModel, FilterLinkViewModel>(this.TypedViewModel);
+        // }
+        // return FilterModel.GetFilterModelsRecursive(graphNode, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
+    }
+
+    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(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];
+    }
+
+    @computed
+    get createOperationParamsCache() {
+        return this.CreateOperationParameters();
+    }
+
+    public QRange: QuantitativeBinRange | undefined;
+
+    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: Main.Instance.ActiveSchema!.displayName,
+                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.TypedViewModel.BrushColors = this.TypedViewModel.BrusherModels.map(e => e.Color);
+        return super.Update();
+    }
+}
+
+
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
new file mode 100644
index 000000000..f35c98317
--- /dev/null
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -0,0 +1,90 @@
+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 (let i = 0; i < arr1.length; i++) {
+            if (isComplex && "Equals" in arr1[i]) {
+                if (arr1[i].Equals(arr2)) {
+                    return true;
+                }
+            }
+            else {
+                if (arr1[i] === 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 (let i = 0; i < arr.length; i++) {
+            if (!ArrayUtil.Contains(ret, arr[i])) {
+                ret.push(arr[i]);
+            }
+        }
+        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
new file mode 100644
index 000000000..71bcadf89
--- /dev/null
+++ b/src/client/northstar/utils/Extensions.ts
@@ -0,0 +1,20 @@
+interface String {
+    ReplaceAll(toReplace: string, replacement: string): string;
+}
+
+String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string {
+    var target = this;
+    return target.split(toReplace).join(replacement);
+}
+
+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
new file mode 100644
index 000000000..d5f3ba631
--- /dev/null
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -0,0 +1,129 @@
+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 (var i = 0; i < points.length; i++) {
+            if (points[i].x < minX)
+                minX = points[i].x;
+            if (points[i].y < minY)
+                minY = points[i].y;
+            if (points[i].x > maxX)
+                maxX = points[i].x;
+            if (points[i].y > maxY)
+                maxY = points[i].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 (let v = 0; v < points.length; v++) {
+            x = points[v].x;
+            y = points[v].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 (let v = 0; v < points.length; v++) {
+            x = points[v].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
new file mode 100644
index 000000000..5e9843326
--- /dev/null
+++ b/src/client/northstar/utils/IDisposable.ts
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 000000000..2f81c2478
--- /dev/null
+++ b/src/client/northstar/utils/IEquatable.ts
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 000000000..044569ffe
--- /dev/null
+++ b/src/client/northstar/utils/KeyCodes.ts
@@ -0,0 +1,137 @@
+/**
+ * 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/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
new file mode 100644
index 000000000..3ed8628ee
--- /dev/null
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -0,0 +1,236 @@
+
+
+export class PIXIPoint {
+    public x: number;
+    public y: number;
+    constructor(x: number, y: number) {
+        this.x = x;
+        this.y = 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 }
+    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
+        var 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.
+        var dot = MathUtil.Dot(
+            MathUtil.SubtractPoint(p, v),
+            MathUtil.SubtractPoint(w, v)) / l2;
+        var t = Math.max(0, Math.min(1, dot));
+        // Projection falls on the segment
+        var 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 {
+        var a1 = pe1.y - ps1.y;
+        var b1 = ps1.x - pe1.x;
+
+        var a2 = pe2.y - ps2.y;
+        var b2 = ps2.x - pe2.x;
+
+        var delta = a1 * b2 - a2 * b1;
+        if (delta == 0) {
+            return undefined;
+        }
+        var c2 = a2 * ps2.x + b2 * ps2.y;
+        var c1 = a1 * ps1.x + b1 * ps1.y;
+        var 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> {
+        var r1 = new PIXIPoint(rect.left, rect.top);
+        var r2 = new PIXIPoint(rect.right, rect.top);
+        var r3 = new PIXIPoint(rect.right, rect.bottom);
+        var r4 = new PIXIPoint(rect.left, rect.bottom);
+        var ret = new Array<PIXIPoint>();
+        var dist = this.Dist(lineFrom, lineTo)
+        var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
+        if (inter != null && 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 != null && 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 != null && 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 != null && 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) {
+        var 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[]) {
+        let result = new Array<T>();
+        let 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
new file mode 100644
index 000000000..2f20de96f
--- /dev/null
+++ b/src/client/northstar/utils/PartialClass.ts
@@ -0,0 +1,7 @@
+
+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/Utils.ts b/src/client/northstar/utils/Utils.ts
new file mode 100644
index 000000000..b35dce820
--- /dev/null
+++ b/src/client/northstar/utils/Utils.ts
@@ -0,0 +1,75 @@
+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 (let i = 0; i < vars.length; i++) {
+            let pair = vars[i].split("=");
+            if (decodeURIComponent(pair[0]) == variable) {
+                return decodeURIComponent(pair[1]);
+            }
+        }
+        return undefined;
+    }
+}
+
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index ac51a7d87..b27f63e52 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,4 +1,4 @@
-import { action, configure, observable, runInAction, trace, computed } from 'mobx';
+import { action, configure, observable, runInAction, trace, computed, reaction } from 'mobx';
 import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
@@ -22,7 +22,6 @@ import "./Main.scss";
 import { observer } from 'mobx-react';
 import { InkingControl } from './InkingControl';
 import { RouteStore } from '../../server/RouteStore';
-import { json } from 'body-parser';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faFont } from '@fortawesome/free-solid-svg-icons';
@@ -42,9 +41,10 @@ import { ServerUtils } from '../../server/ServerUtil';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { Field, Opt } from '../../fields/Field';
 import { ListField } from '../../fields/ListField';
-import { map } from 'bluebird';
 import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { Catalog } from '../northstar/model/idea/idea';
+import { Catalog, Schema, Attribute, AttributeGroup } from '../northstar/model/idea/idea';
+import { ArrayUtil } from '../northstar/utils/ArrayUtil';
+import '../northstar/model/ModelExtensions'
 
 @observer
 export class Main extends React.Component {
@@ -88,16 +88,45 @@ export class Main extends React.Component {
 
         this.initEventListeners();
         Documents.initProtos(() => this.initAuthenticationRouters());
+
+        reaction(() => [this.mainContainer, this.ActiveSchema],
+            () => {
+                if (this.mainContainer && this.ActiveSchema) {
+                    if (!this.mainContainer!.GetTAsync(KeyStore.ActiveDB, ListField, field => this.NorthstarCatalog = field!.Data as Document[])) {
+                        this.NorthstarCatalog = this.GetAllAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! }));
+                        this.mainContainer!.SetData(KeyStore.ActiveDB, this.NorthstarCatalog, ListField);
+                    }
+                }
+            })
     }
 
+    NorthstarCatalog: Document[] = [];
+    @observable ActiveSchema: Schema | undefined;
     @action SetNorthstarCatalog(ctlog: Catalog) {
         this._northstarCatalog = ctlog;
-        if (this._northstarCatalog) {
+        if (this._northstarCatalog && this._northstarCatalog.schemas) {
             console.log("CATALOG " + this._northstarCatalog.schemas);
+            this.ActiveSchema = ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas!, (s: Schema) => s.displayName === "mimic");
         }
     }
+    public GetAllAttributes() {
+        if (!this.ActiveSchema || !this.ActiveSchema.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, this.ActiveSchema.rootAttributeGroup);
+        return allAttributes;
+    }
     async initializeNorthstar(): Promise<void> {
-        let envPath = "assets/env.json";
+        let envPath = "/assets/env.json";
         const response = await fetch(envPath, {
             redirect: "follow",
             method: "GET",
@@ -251,7 +280,7 @@ export class Main extends React.Component {
 
         let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
         let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
-        let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
+        let addSchemaNode = action(() => Documents.SchemaDocument(this.NorthstarCatalog, { width: 200, height: 200, title: "a schema collection" }));
         let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
         let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
         let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
@@ -269,7 +298,9 @@ export class Main extends React.Component {
             [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
         ]
 
-        let addClick = (creator: () => Document) => action(() => this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator()));
+        let addClick = (creator: () => Document) => action(() => {
+            this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator())
+        });
 
         return < div id="add-nodes-menu" >
             <input type="checkbox" id="add-menu-toggle" />
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ce72ab64b..2f0459f88 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,6 +19,7 @@ import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
+import { HistogramBox } from "./HistogramBox";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -47,8 +48,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-            bindings={this.CreateBindings()}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
             onError={(test: any) => { console.log(test) }}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b6d50bffb..f6343c631 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -7,7 +7,6 @@ import { TextField } from "../../../fields/TextField";
 import { NumberField } from "../../../fields/NumberField";
 import { RichTextField } from "../../../fields/RichTextField";
 import { ImageField } from "../../../fields/ImageField";
-import { WebField } from "../../../fields/WebField";
 import { VideoField } from "../../../fields/VideoField"
 import { Key } from "../../../fields/Key";
 import { FormattedTextBox } from "./FormattedTextBox";
@@ -64,9 +63,11 @@ export class FieldView extends React.Component<FieldViewProps> {
         }
         else if (field instanceof AudioField) {
             return <AudioBox {...this.props} />
-        } else if (field instanceof Document) {
+        }
+        else if (field instanceof Document) {
             return <div>{field.Title}</div>
-        } else if (field instanceof ListField) {
+        }
+        else if (field instanceof ListField) {
             return (<div>
                 {(field as ListField<Field>).Data.map(f => {
                     return f instanceof Document ? f.Title : f.GetValue().toString();
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
new file mode 100644
index 000000000..04bf1d732
--- /dev/null
+++ b/src/client/views/nodes/HistogramBox.scss
@@ -0,0 +1,8 @@
+.histogrambox-container {
+    padding: 0vw;
+    position: relative;
+    text-align: center;
+    width: 100%;
+    height: 100%;
+  }
+  
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
new file mode 100644
index 000000000..0fcc25e66
--- /dev/null
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -0,0 +1,67 @@
+import React = require("react")
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./VideoBox.scss";
+import { observable, reaction } from "mobx";
+import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
+import { Main } from "../Main";
+import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { AggregateFunction, HistogramResult, DoubleValueAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+
+@observer
+export class HistogramBox extends React.Component<FieldViewProps> {
+
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+
+    constructor(props: FieldViewProps) {
+        super(props);
+    }
+
+    @observable _histoResult?: HistogramResult;
+    _histoOp?: HistogramOperation;
+
+    componentDidMount() {
+        Main.Instance.GetAllAttributes().map(a => {
+            if (a.displayName == this.props.doc.Title) {
+                var atmod = new ColumnAttributeModel(a);
+                this._histoOp = new HistogramOperation(new AttributeTransformationModel(atmod, AggregateFunction.None),
+                    new AttributeTransformationModel(atmod, AggregateFunction.Count),
+                    new AttributeTransformationModel(atmod, AggregateFunction.Count));
+                reaction(() => [this._histoOp && this._histoOp.Result],
+                    () => this._histoResult = this._histoOp ? this._histoOp.Result as HistogramResult : undefined
+                );
+                this._histoOp.Update();
+            }
+        })
+    }
+
+    twoString() {
+        let str = "";
+        if (this._histoResult && !this._histoResult.isEmpty) {
+            for (let key in this._histoResult.bins) {
+                if (this._histoResult.bins.hasOwnProperty(key)) {
+                    let bin = this._histoResult.bins[key];
+                    str += JSON.stringify(bin.binIndex!.toJSON()) + " = ";
+                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this._histoOp!.V, this._histoResult, ModelHelpers.AllBrushIndex(this._histoResult));
+                    let value = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+                    if (value && value.hasResult && value.result) {
+                        str += value.result;
+                    }
+                }
+            }
+        }
+        return str;
+    }
+
+    render() {
+        if (!this._histoResult)
+            return (null);
+        return (
+            <div className="histogrambox-container">
+                `HISTOGRAM RESULT : ${this.twoString()}`
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 68883d6f1..319702cc4 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -28,6 +28,7 @@ export namespace KeyStore {
     export const SchemaSplitPercentage = new Key("SchemaSplitPercentage");
     export const Caption = new Key("Caption");
     export const ActiveFrame = new Key("ActiveFrame");
+    export const ActiveDB = new Key("ActiveDB");
     export const DocumentText = new Key("DocumentText");
     export const LinkedToDocs = new Key("LinkedToDocs");
     export const LinkedFromDocs = new Key("LinkedFromDocs");
-- 
cgit v1.2.3-70-g09d2


From 7ffbfd93f6c7c66993e0f447ba10a06386512c75 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 22 Mar 2019 02:59:11 -0400
Subject: Moved LayoutKeys check

---
 src/client/views/nodes/DocumentContentsView.tsx | 7 ++++++-
 src/client/views/nodes/DocumentView.tsx         | 4 ----
 2 files changed, 6 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 2f0459f88..12d14eb42 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -47,8 +47,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
+        if (!lkeys || lkeys === FieldWaiting) {
+            return <p>Error loading layout keys</p>;
+        }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
             onError={(test: any) => { console.log(test) }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fec451b09..cfe67c8fe 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -314,10 +314,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (!this.props.Document) {
             return (null);
         }
-        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
-        if (!lkeys || lkeys === FieldWaiting) {
-            return <p>Error loading layout keys</p>;
-        }
         var scaling = this.props.ContentScaling();
         var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
         var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
-- 
cgit v1.2.3-70-g09d2


From e9826e0ac334bac28d173f67b4f0db0800b40680 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 26 Mar 2019 16:06:06 -0400
Subject: cleaned up a bit. got rid of blinking when histos change.

---
 src/client/northstar/core/filter/FilterModel.ts    |  50 +--
 src/client/northstar/operations/BaseOperation.ts   |   3 +-
 .../northstar/operations/HistogramOperation.ts     |   1 -
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 src/client/views/nodes/HistogramBox.tsx            | 360 ++------------------
 src/client/views/nodes/HistogramBoxPrimitives.tsx  | 373 +++++++++++++++++++++
 6 files changed, 399 insertions(+), 391 deletions(-)
 create mode 100644 src/client/views/nodes/HistogramBoxPrimitives.tsx

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

diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index a9c79d245..bc7938947 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -1,11 +1,9 @@
 import { ValueComparison } from "./ValueComparision";
 import { Utils } from "../../utils/Utils";
-import { IBaseFilterProvider, instanceOfIBaseFilterProvider } from "./IBaseFilterProvider";
-import { BaseOperation } from "../../operations/BaseOperation";
+import { IBaseFilterProvider } from "./IBaseFilterProvider";
 import { FilterOperand } from "./FilterOperand";
 import { HistogramField } from "../../../../fields/HistogramField";
 import { KeyStore } from "../../../../fields/KeyStore";
-import { filter } from "bluebird";
 import { FieldWaiting } from "../../../../fields/Field";
 import { Document } from "../../../../fields/Document";
 
@@ -35,8 +33,7 @@ export class FilterModel {
     }
 
     public ToPythonString(): string {
-        let ret = "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
-        return ret;
+        return "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
     }
 
     public static And(filters: string[]): string {
@@ -84,47 +81,4 @@ export class FilterModel {
         }
         return ret;
     }
-
-    // public static GetFilterModelsRecursive(baseOperation: BaseOperation,
-    //     visitedFilterProviders: Set<IBaseFilterProvider>, filterModels: FilterModel[], isFirst: boolean): string {
-    //     let ret = "";
-    //     if (Utils.isBaseFilterProvider(baseOperation)) {
-    //         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.get(LinkType.Filter);
-    //         if (linkedGraphNodes != null) {
-    //             for (let i = 0; i < linkedGraphNodes.length; i++) {
-    //                 let linkVm = linkedGraphNodes[i].Data;
-    //                 let linkedGraphNode = linkedGraphNodes[i].Target;
-    //                 if (!visitedFilterProviders.has(linkedGraphNode)) {
-    //                     let child = FilterModel.GetFilterModelsRecursive(linkedGraphNode, 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/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 4bc8873d5..7db6fcb91 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -61,7 +61,8 @@ export abstract class BaseOperation {
             }
 
             let operationParameters = this.CreateOperationParameters();
-            this.Result = undefined;
+            if (this.Result)
+                this.Result.progress = 0; // bcz: used to set Result to undefined, but that causes the display to blink
             this.Error = "";
             let salt = Math.random().toString();
             this.RequestSalt = salt;
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 93946b296..bceadb961 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -55,7 +55,6 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     public get FilterString(): string {
         let filterModels: FilterModel[] = [];
         let fstring = FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
-        console.log("Filter string " + this.X.AttributeModel.DisplayName + " = " + fstring);
         return fstring;
     }
     @computed
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 12d14eb42..40990b76a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -20,6 +20,7 @@ import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "./HistogramBox";
+import { HistogramBoxPrimitives } from "./HistogramBoxPrimitives";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -52,7 +53,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             return <p>Error loading layout keys</p>;
         }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, HistogramBoxPrimitives }}
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
index e21054e15..20a022227 100644
--- a/src/client/views/nodes/HistogramBox.tsx
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -13,16 +13,15 @@ import { FilterModel } from '../../northstar/core/filter/FilterModel';
 import { NominalVisualBinRange } from "../../northstar/model/binRanges/NominalVisualBinRange";
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, AggregateFunction, Bin, BinRange, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
-import { LABColor } from '../../northstar/utils/LABcolor';
 import { PIXIRectangle } from "../../northstar/utils/MathUtil";
 import { SizeConverter } from "../../northstar/utils/SizeConverter";
 import { StyleConstants } from "../../northstar/utils/StyleContants";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./HistogramBox.scss";
+import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
@@ -41,6 +40,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
     @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
+    @computed get createOperationParamsCache() { return this.HistoOp!.CreateOperationParameters(); }
 
     componentDidMount() {
         reaction(() => [CurrentUserUtils.ActiveSchemaName, this.props.doc.GetText(KeyStore.NorthstarSchema, "?")],
@@ -57,8 +57,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                     binRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
 
                 this.VisualBinRanges.length = 0;
-                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[0], this.HistoOp.Result, this.HistoOp.X, this.ChartType));
-                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[1], this.HistoOp.Result, this.HistoOp.Y, this.ChartType));
+                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[0], this.HistoOp!.Result!, this.HistoOp!.X, this.ChartType));
+                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[1], this.HistoOp!.Result!, this.HistoOp!.Y, this.ChartType));
 
                 if (!this.HistoOp.Result.isEmpty) {
                     this.MaxValue = Number.MIN_VALUE;
@@ -79,11 +79,6 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         );
     }
 
-    @computed
-    get createOperationParamsCache() {
-        return this.HistoOp!.CreateOperationParameters();
-    }
-
     activateHistogramOperation() {
         this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => {
             if (histoOp) {
@@ -91,7 +86,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                 this.HistoOp!.Update();
                 reaction(
                     () => this.createOperationParamsCache,
-                    () => this.HistoOp!.Update();
+                    () => this.HistoOp!.Update());
                 reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []),
                     (docs: Document[]) => {
                         this.HistoOp!.Links.length = 0;
@@ -107,22 +102,16 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         return <div key={DashUtils.GenerateGuid()} style={{ position: "absolute", width: `${width}px`, height: `${height}px`, background: "lightgray", transform: `translate(${xFrom}px, ${yFrom}px)` }} />;
     }
 
-    drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
-        return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler} style={{ position: "absolute", transform: `translate(${r.x}px,${r.y}px)`, width: `${r.width - 1}`, height: `${r.height}`, background: LABColor.RGBtoHexString(color) }} />
-    }
-
     private renderGridLinesAndLabels(axis: number) {
         let sc = this.SizeConverter!;
-        let labels = this.VisualBinRanges[axis].GetLabels();
-
-        let dim = sc.RenderSize[axis] / sc.MaxLabelSizes[axis].coords[axis] + 5;
-        let mod = Math.ceil(labels.length / dim);
+        if (!sc || !this.VisualBinRanges.length)
+            return (null);
+        let dim = sc.RenderSize[axis] / ((axis == 0 && this.VisualBinRanges[axis] instanceof NominalVisualBinRange) ?
+            (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
+            sc.MaxLabelSizes[axis].coords[axis] + 5);
 
-        if (axis == 0 && this.VisualBinRanges[axis] instanceof NominalVisualBinRange) {
-            mod = Math.ceil(
-                labels.length / (sc.RenderSize[0] / (12 + 5))); //  (<number>FontStyles.AxisLabel.fontSize + 5)));
-        }
         let prims: JSX.Element[] = [];
+        let labels = this.VisualBinRanges[axis].GetLabels();
         labels.map((binLabel, i) => {
             let xFrom = sc.DataToScreenX(axis === 0 ? binLabel.minValue! : sc.DataMins[0]);
             let xTo = sc.DataToScreenX(axis === 0 ? binLabel.maxValue! : sc.DataMaxs[0]);
@@ -133,7 +122,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             if (i == labels.length - 1)
                 prims.push(this.drawLine(axis == 0 ? xTo : xFrom, axis == 0 ? yFrom : yTo, axis == 0 ? 1 : xTo - xFrom, axis == 0 ? yTo - yFrom : 1));
 
-            if (i % mod === 0 && binLabel.label) {
+            if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) {
                 let text = binLabel.label;
                 if (text.length >= StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS) {
                     text = text.slice(0, StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS - 3) + "...";
@@ -157,330 +146,21 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         return prims;
     }
 
-    @computed
-    get binPrimitives() {
-        if (!this.HistoOp || !(this.HistoOp.Result instanceof HistogramResult) || !this.SizeConverter)
-            return undefined;
-        let prims: JSX.Element[] = [];
-        let selectedBinPrimitiveCollections = new Array<HistogramBinPrimitiveCollection>();
-        let allBrushIndex = ModelHelpers.AllBrushIndex(this.HistoOp.Result);
-        for (let key in this.HistoOp.Result.bins) {
-            if (this.HistoOp.Result.bins.hasOwnProperty(key)) {
-                let drawPrims = new HistogramBinPrimitiveCollection(key, this);
-
-                let filterModel = ModelHelpers.GetBinFilterModel(this.HistoOp.Result.bins![key], allBrushIndex, this.HistoOp.Result, this.HistoOp.X, this.HistoOp.Y);
-
-                this.HitTargets.setValue(drawPrims.HitGeom, filterModel);
-
-                if (ArrayUtil.Contains(this.HistoOp.FilterModels, filterModel)) {
-                    selectedBinPrimitiveCollections.push(drawPrims);
-                }
-
-                drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(binPrimitive => {
-                    let toggleFilter = () => {
-                        if ([filterModel].filter(h => ArrayUtil.Contains(this.HistoOp!.FilterModels, h)).length > 0)
-                            this.HistoOp!.RemoveFilterModels([filterModel]);
-                        else this.HistoOp!.AddFilterModels([filterModel]);
-                    }
-                    prims.push(this.drawRect(binPrimitive.Rect, binPrimitive.Color, () => runInAction(toggleFilter)));
-                    prims.push(this.drawRect(binPrimitive.MarginRect, StyleConstants.MARGIN_BARS_COLOR, () => runInAction(toggleFilter)));
-                });
-            }
-        }
-        return prims;
-    }
-
     render() {
-        trace();
-        if (!this.binPrimitives || !this.VisualBinRanges.length) {
-            return (null);
-        }
-
+        let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>";
+        let xaxislines = this.xaxislines;
+        let yaxislines = this.yaxislines;
         return (
             <Measure onResize={(r: any) => runInAction(() => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height })}>
                 {({ measureRef }) =>
                     <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-this.props.doc.GetNumber(KeyStore.Width, 0) / 2}px, ${-this.props.doc.GetNumber(KeyStore.Height, 0) / 2}px)` }}>
-                        {this.xaxislines}
-                        {this.yaxislines}
-                        {this.binPrimitives}
-                        <div className="histogrambox-xaxislabel">{this.HistoOp!.X.AttributeModel.DisplayName}</div>
+                        {xaxislines}
+                        {yaxislines}
+                        <HistogramBoxPrimitives HistoBox={this} />
+                        <div className="histogrambox-xaxislabel">{label}</div>
                     </div>
                 }
             </Measure>
         )
     }
-}
-
-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;
-}
-
-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; }
-    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
-    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
-
-    constructor(key: string, histoBox: HistogramBox) {
-        this._histoBox = histoBox;
-        let bin = this.histoResult.bins![key];
-
-        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
-        var orderedBrushes = new Array<Brush>();
-        orderedBrushes.push(this.histoResult.brushes![0]);
-        orderedBrushes.push(this.histoResult.brushes![overlapBrushIndex]);
-        for (var b = 0; b < this.histoResult.brushes!.length; b++) {
-            var brush = this.histoResult.brushes![b];
-            if (brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex) {
-                orderedBrushes.push(brush);
-            }
-        }
-        var binBrushMaxAxis = this.getBinBrushAxisRange(bin, orderedBrushes, this.histoOp.Normalization); // X= 0, Y = 1
-
-        var brushFactorSum: number = 0;
-        for (var b = 0; b < orderedBrushes.length; b++) {
-            var brush = orderedBrushes[b];
-            var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, brush.brushIndex!);
-            var doubleRes = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
-            var unNormalizedValue = (doubleRes != null && doubleRes.hasResult) ? doubleRes.result : null;
-            if (unNormalizedValue)
-                switch (histoBox.ChartType) {
-                    case ChartType.VerticalBar:
-                        this.createVerticalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!); // X = 0, Y = 1, NOne = -1
-                        break;
-                    case ChartType.HorizontalBar:
-                        this.createHorizontalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!);
-                        break;
-                    case ChartType.SinglePoint:
-                        this.createSinlgePointChartBinPrimitives(bin, brush, unNormalizedValue, histoBox.SizeConverter!);
-                        break;
-                    case ChartType.HeatMap:
-                        var normalizedValue = (unNormalizedValue - histoBox.MinValue) / (Math.abs((histoBox.MaxValue - histoBox.MinValue)) < HistogramBinPrimitiveCollection.TOLERANCE ?
-                            unNormalizedValue : histoBox.MaxValue - histoBox.MinValue);
-                        brushFactorSum = this.createHeatmapBinPrimitives(bin, brush, unNormalizedValue, brushFactorSum, normalizedValue, histoBox.SizeConverter!);
-                }
-        }
-
-        // adjust brush rects (stacking or not)
-        var sum: number = 0;
-        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
-        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
-        var count: number = filteredBinPrims.length;
-        filteredBinPrims.map(fbp => {
-            if (histoBox.ChartType == ChartType.VerticalBar) {
-                if (this.histoOp.X.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);
-                    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 / count, 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);
-                    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);
-                    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 / count);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
-                    sum += fbp.Rect.height;
-                }
-            }
-        });
-        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 getBinBrushAxisRange(bin: Bin, brushes: Array<Brush>, axis: number): number {
-        var binBrushMaxAxis = Number.MIN_VALUE;
-        brushes.forEach((Brush) => {
-            var maxAggregateKey = ModelHelpers.CreateAggregateKey(axis === 0 ? this.histoOp.Y : this.histoOp.X, this.histoResult, Brush.brushIndex!);
-            var aggResult = ModelHelpers.GetAggregateResult(bin, maxAggregateKey) as DoubleValueAggregateResult;
-            if (aggResult != null) {
-                if (aggResult.result! > binBrushMaxAxis)
-                    binBrushMaxAxis = aggResult.result!;
-            }
-        });
-        return binBrushMaxAxis;
-    }
-    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, brushFactorSum: number, normalizedValue: number, sizeConverter: SizeConverter): number {
-
-        var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult));
-        var allUnNormalizedValue = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
-
-        var tx = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0]);
-        var xFrom = sizeConverter.DataToScreenX(tx);
-        var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(tx));
-
-        var ty = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
-        var yFrom = sizeConverter.DataToScreenY(ty);
-        var yTo = sizeConverter.DataToScreenY(this._histoBox.VisualBinRanges[1].AddStep(ty));
-
-        var returnBrushFactorSum = brushFactorSum;
-        if (allUnNormalizedValue.hasResult) {
-            var brushFactor = (unNormalizedValue / allUnNormalizedValue.result!);
-            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(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
-        return returnBrushFactorSum;
-    }
-
-    private createSinlgePointChartBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, sizeConverter: SizeConverter): void {
-        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
-        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
-
-        var xValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
-        if (!xValue.hasResult)
-            return;
-        var xFrom = sizeConverter.DataToScreenX(xValue.result!) - 5;
-        var xTo = sizeConverter.DataToScreenX(xValue.result!) + 5;
-
-        var yValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;;
-        if (!yValue.hasResult)
-            return;
-        var yFrom = sizeConverter.DataToScreenY(yValue.result!) + 5;
-        var yTo = sizeConverter.DataToScreenY(yValue.result!);
-
-        this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
-    }
-
-    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
-        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = this.histoOp.Y.AggregateFunction;
-        var yMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult,
-            brush.brushIndex!, marginParams);
-        var dataValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;
-
-        if (dataValue != null && dataValue.hasResult) {
-            var yValue = normalization != 0 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[1];
-
-            var yFrom = sizeConverter.DataToScreenY(Math.min(0, yValue));
-            var yTo = sizeConverter.DataToScreenY(Math.max(0, yValue));;
-
-            var xValue = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0])!;
-            var xFrom = sizeConverter.DataToScreenX(xValue);
-            var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(xValue));
-
-            var marginResult = ModelHelpers.GetAggregateResult(bin, yMarginAggregateKey) as MarginAggregateResult;
-            var yMarginAbsolute = !marginResult ? 0 : marginResult.absolutMargin!;
-            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
-                sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
-                sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
-
-            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[1] + 0.4, dataValue.result!);
-        }
-    }
-
-    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
-        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = this.histoOp.X.AggregateFunction;
-        var xMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult,
-            brush.brushIndex!, marginParams);
-        var dataValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
-
-        if (dataValue != null && dataValue.hasResult) {
-            var xValue = normalization != 1 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[0];
-            var xFrom = sizeConverter.DataToScreenX(Math.min(0, xValue));
-            var xTo = sizeConverter.DataToScreenX(Math.max(0, xValue));
-
-            var yValue = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
-            var yFrom = yValue;
-            var yTo = this._histoBox.VisualBinRanges[1].AddStep(yValue);
-
-            var marginResult = ModelHelpers.GetAggregateResult(bin, xMarginAggregateKey) as MarginAggregateResult;
-            var xMarginAbsolute = sizeConverter.IsSmall || !marginResult ? 0 : marginResult.absolutMargin!;
-
-            var marginRect = new PIXIRectangle(sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                yTo + (yFrom - yTo) / 2.0 - 1,
-                sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                2.0);
-
-            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[0] + 0.4, dataValue.result!);
-        }
-    }
-
-    private createBinPrimitive(bin: Bin, brush: Brush, marginRect: PIXIRectangle,
-        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
-        // hitgeom todo
-
-        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
-            });
-        this.BinPrimitives.push(binPrimitive);
-    }
-
-    private baseColorFromBrush(brush: Brush): number {
-        var baseColor: number = StyleConstants.HIGHLIGHT_COLOR;
-        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
-            baseColor = StyleConstants.HIGHLIGHT_COLOR;
-        }
-        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
-            baseColor = StyleConstants.OVERLAP_COLOR;
-        }
-        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
-            baseColor = 0x00ff00;
-        }
-        else {
-            if (this._histoBox.HistoOp!.BrushColors.length > 0) {
-                baseColor = this._histoBox.HistoOp!.BrushColors[brush.brushIndex! % this._histoBox.HistoOp!.BrushColors.length];
-            }
-            else {
-                baseColor = StyleConstants.HIGHLIGHT_COLOR;
-            }
-        }
-        return baseColor;
-    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
new file mode 100644
index 000000000..2f4a553b7
--- /dev/null
+++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx
@@ -0,0 +1,373 @@
+import React = require("react")
+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 { SizeConverter } from "../../northstar/utils/SizeConverter";
+import { StyleConstants } from "../../northstar/utils/StyleContants";
+import "./HistogramBox.scss";
+import { HistogramBox } from "./HistogramBox";
+import { computed, runInAction, observable, trace } from "mobx";
+import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
+import { Utils as DashUtils } from '../../../Utils';
+import { observer } from "mobx-react";
+
+
+export interface HistogramBoxPrimitivesProps {
+    HistoBox: HistogramBox;
+}
+
+@observer
+export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitivesProps> {
+    @observable _selectedPrims: HistogramBinPrimitive[] = [];
+
+    @computed
+    get selectedPrimitives() {
+        return this._selectedPrims.map((bp) => this.drawBorder(bp.Rect, StyleConstants.OPERATOR_BACKGROUND_COLOR));
+    }
+    @computed
+    get binPrimitives() {
+        if (!this.props.HistoBox.HistoOp || !(this.props.HistoBox.HistoOp.Result instanceof HistogramResult) || !this.props.HistoBox.SizeConverter)
+            return (null);
+        let prims: JSX.Element[] = [];
+        let allBrushIndex = ModelHelpers.AllBrushIndex(this.props.HistoBox.HistoOp.Result);
+        for (let key in this.props.HistoBox.HistoOp.Result.bins) {
+            if (this.props.HistoBox.HistoOp.Result.bins.hasOwnProperty(key)) {
+                let drawPrims = new HistogramBinPrimitiveCollection(key, this.props.HistoBox);
+                let filterModel = ModelHelpers.GetBinFilterModel(this.props.HistoBox.HistoOp.Result.bins![key], allBrushIndex, this.props.HistoBox.HistoOp.Result, this.props.HistoBox.HistoOp.X, this.props.HistoBox.HistoOp.Y);
+
+                this.props.HistoBox.HitTargets.setValue(drawPrims.HitGeom, filterModel);
+
+                drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(binPrimitive => {
+                    let toggleFilter = () => {
+                        if ([filterModel].filter(h => ArrayUtil.Contains(this.props.HistoBox.HistoOp!.FilterModels, h)).length > 0) {
+                            let bp = ArrayUtil.FirstOrDefault<HistogramBinPrimitive>(drawPrims.BinPrimitives, (bp: HistogramBinPrimitive) => bp.BrushIndex == allBrushIndex);
+                            if (bp && bp.DataValue) {
+                                this._selectedPrims.splice(this._selectedPrims.indexOf(bp), 1);
+                            }
+                            this.props.HistoBox.HistoOp!.RemoveFilterModels([filterModel]);
+                        }
+                        else {
+                            let bp = ArrayUtil.FirstOrDefault<HistogramBinPrimitive>(drawPrims.BinPrimitives, (bp: HistogramBinPrimitive) => bp.BrushIndex == allBrushIndex);
+                            if (bp && bp.DataValue) {
+                                this._selectedPrims.push(bp!);
+                            }
+                            this.props.HistoBox.HistoOp!.AddFilterModels([filterModel]);
+                        }
+                    }
+                    prims.push(this.drawRect(binPrimitive.Rect, binPrimitive.Color, () => runInAction(toggleFilter)));
+                    prims.push(this.drawRect(binPrimitive.MarginRect, StyleConstants.MARGIN_BARS_COLOR, () => runInAction(toggleFilter)));
+                });
+            }
+        }
+        return prims;
+    }
+    drawBorder(r: PIXIRectangle, color: number) {
+        return <div key={DashUtils.GenerateGuid()} className="histogramboxprimitive-border"
+            style={{
+                position: "absolute",
+                transform: `translate(${r.x}px,${r.y}px)`,
+                width: `${r.width - 1}`,
+                height: `${r.height}`,
+                border: "1px",
+                borderStyle: "solid",
+                pointerEvents: "none",
+                borderColor: `${LABColor.RGBtoHexString(color)}`
+            }}
+        />
+    }
+
+    drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
+        return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler}
+            style={{
+                position: "absolute",
+                transform: `translate(${r.x}px,${r.y}px)`,
+                width: `${r.width - 1}`,
+                height: `${r.height}`,
+                background: `${LABColor.RGBtoHexString(color)}`
+            }}
+        />
+    }
+    render() {
+        return <div>
+            {this.binPrimitives}
+            {this.selectedPrimitives}
+        </div>
+    }
+}
+
+
+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;
+}
+
+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; }
+    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
+    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
+
+    constructor(key: string, histoBox: HistogramBox) {
+        this._histoBox = histoBox;
+        let bin = this.histoResult.bins![key];
+
+        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
+        var orderedBrushes = new Array<Brush>();
+        orderedBrushes.push(this.histoResult.brushes![0]);
+        orderedBrushes.push(this.histoResult.brushes![overlapBrushIndex]);
+        for (var b = 0; b < this.histoResult.brushes!.length; b++) {
+            var brush = this.histoResult.brushes![b];
+            if (brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex) {
+                orderedBrushes.push(brush);
+            }
+        }
+        var binBrushMaxAxis = this.getBinBrushAxisRange(bin, orderedBrushes, this.histoOp.Normalization); // X= 0, Y = 1
+
+        var brushFactorSum: number = 0;
+        for (var b = 0; b < orderedBrushes.length; b++) {
+            var brush = orderedBrushes[b];
+            var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, brush.brushIndex!);
+            var doubleRes = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+            var unNormalizedValue = (doubleRes != null && doubleRes.hasResult) ? doubleRes.result : null;
+            if (unNormalizedValue)
+                switch (histoBox.ChartType) {
+                    case ChartType.VerticalBar:
+                        this.createVerticalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!); // X = 0, Y = 1, NOne = -1
+                        break;
+                    case ChartType.HorizontalBar:
+                        this.createHorizontalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!);
+                        break;
+                    case ChartType.SinglePoint:
+                        this.createSinlgePointChartBinPrimitives(bin, brush, unNormalizedValue, histoBox.SizeConverter!);
+                        break;
+                    case ChartType.HeatMap:
+                        var normalizedValue = (unNormalizedValue - histoBox.MinValue) / (Math.abs((histoBox.MaxValue - histoBox.MinValue)) < HistogramBinPrimitiveCollection.TOLERANCE ?
+                            unNormalizedValue : histoBox.MaxValue - histoBox.MinValue);
+                        brushFactorSum = this.createHeatmapBinPrimitives(bin, brush, unNormalizedValue, brushFactorSum, normalizedValue, histoBox.SizeConverter!);
+                }
+        }
+
+        // adjust brush rects (stacking or not)
+        var sum: number = 0;
+        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
+        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
+        var count: number = filteredBinPrims.length;
+        filteredBinPrims.map(fbp => {
+            if (histoBox.ChartType == ChartType.VerticalBar) {
+                if (this.histoOp.X.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);
+                    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 / count, 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);
+                    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);
+                    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 / count);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
+                    sum += fbp.Rect.height;
+                }
+            }
+        });
+        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 getBinBrushAxisRange(bin: Bin, brushes: Array<Brush>, axis: number): number {
+        var binBrushMaxAxis = Number.MIN_VALUE;
+        brushes.forEach((Brush) => {
+            var maxAggregateKey = ModelHelpers.CreateAggregateKey(axis === 0 ? this.histoOp.Y : this.histoOp.X, this.histoResult, Brush.brushIndex!);
+            var aggResult = ModelHelpers.GetAggregateResult(bin, maxAggregateKey) as DoubleValueAggregateResult;
+            if (aggResult != null) {
+                if (aggResult.result! > binBrushMaxAxis)
+                    binBrushMaxAxis = aggResult.result!;
+            }
+        });
+        return binBrushMaxAxis;
+    }
+    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, brushFactorSum: number, normalizedValue: number, sizeConverter: SizeConverter): number {
+
+        var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult));
+        var allUnNormalizedValue = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+
+        var tx = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0]);
+        var xFrom = sizeConverter.DataToScreenX(tx);
+        var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(tx));
+
+        var ty = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
+        var yFrom = sizeConverter.DataToScreenY(ty);
+        var yTo = sizeConverter.DataToScreenY(this._histoBox.VisualBinRanges[1].AddStep(ty));
+
+        var returnBrushFactorSum = brushFactorSum;
+        if (allUnNormalizedValue.hasResult) {
+            var brushFactor = (unNormalizedValue / allUnNormalizedValue.result!);
+            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(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
+        return returnBrushFactorSum;
+    }
+
+    private createSinlgePointChartBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, sizeConverter: SizeConverter): void {
+        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
+        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
+
+        var xValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
+        if (!xValue.hasResult)
+            return;
+        var xFrom = sizeConverter.DataToScreenX(xValue.result!) - 5;
+        var xTo = sizeConverter.DataToScreenX(xValue.result!) + 5;
+
+        var yValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;;
+        if (!yValue.hasResult)
+            return;
+        var yFrom = sizeConverter.DataToScreenY(yValue.result!) + 5;
+        var yTo = sizeConverter.DataToScreenY(yValue.result!);
+
+        this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+    }
+
+    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
+        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
+        var marginParams = new MarginAggregateParameters();
+        marginParams.aggregateFunction = this.histoOp.Y.AggregateFunction;
+        var yMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult,
+            brush.brushIndex!, marginParams);
+        var dataValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;
+
+        if (dataValue != null && dataValue.hasResult) {
+            var yValue = normalization != 0 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[1];
+
+            var yFrom = sizeConverter.DataToScreenY(Math.min(0, yValue));
+            var yTo = sizeConverter.DataToScreenY(Math.max(0, yValue));;
+
+            var xValue = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0])!;
+            var xFrom = sizeConverter.DataToScreenX(xValue);
+            var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(xValue));
+
+            var marginResult = ModelHelpers.GetAggregateResult(bin, yMarginAggregateKey) as MarginAggregateResult;
+            var yMarginAbsolute = !marginResult ? 0 : marginResult.absolutMargin!;
+            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
+                sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
+                sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
+
+            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[1] + 0.4, dataValue.result!);
+        }
+    }
+
+    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
+        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
+        var marginParams = new MarginAggregateParameters();
+        marginParams.aggregateFunction = this.histoOp.X.AggregateFunction;
+        var xMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult,
+            brush.brushIndex!, marginParams);
+        var dataValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
+
+        if (dataValue != null && dataValue.hasResult) {
+            var xValue = normalization != 1 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[0];
+            var xFrom = sizeConverter.DataToScreenX(Math.min(0, xValue));
+            var xTo = sizeConverter.DataToScreenX(Math.max(0, xValue));
+
+            var yValue = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
+            var yFrom = yValue;
+            var yTo = this._histoBox.VisualBinRanges[1].AddStep(yValue);
+
+            var marginResult = ModelHelpers.GetAggregateResult(bin, xMarginAggregateKey) as MarginAggregateResult;
+            var xMarginAbsolute = sizeConverter.IsSmall || !marginResult ? 0 : marginResult.absolutMargin!;
+
+            var marginRect = new PIXIRectangle(sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                yTo + (yFrom - yTo) / 2.0 - 1,
+                sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                2.0);
+
+            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[0] + 0.4, dataValue.result!);
+        }
+    }
+
+    private createBinPrimitive(bin: Bin, brush: Brush, marginRect: PIXIRectangle,
+        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
+        // hitgeom todo
+
+        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
+            });
+        this.BinPrimitives.push(binPrimitive);
+    }
+
+    private baseColorFromBrush(brush: Brush): number {
+        var baseColor: number = StyleConstants.HIGHLIGHT_COLOR;
+        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+            baseColor = StyleConstants.HIGHLIGHT_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+            baseColor = StyleConstants.OVERLAP_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+            baseColor = 0x00ff00;
+        }
+        else {
+            if (this._histoBox.HistoOp!.BrushColors.length > 0) {
+                baseColor = this._histoBox.HistoOp!.BrushColors[brush.brushIndex! % this._histoBox.HistoOp!.BrushColors.length];
+            }
+            else {
+                baseColor = StyleConstants.HIGHLIGHT_COLOR;
+            }
+        }
+        return baseColor;
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 92aaac7cd78b9287d7f6ee85e73b3cad1c42d80c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 26 Mar 2019 16:44:53 -0400
Subject: fixed histos in docking pane and not selecting bars when dragging.

---
 src/client/views/collections/CollectionFreeFormView.tsx | 4 ++--
 src/client/views/nodes/DocumentContentsView.tsx         | 1 +
 src/client/views/nodes/HistogramBox.tsx                 | 4 +++-
 src/client/views/nodes/HistogramBoxPrimitives.tsx       | 2 +-
 4 files changed, 7 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 53fe969fe..1eab3e475 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -279,13 +279,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
     get backgroundView() {
         return !this.backgroundLayout ? (null) :
             (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.BackgroundLayout} isSelected={() => false} select={() => { }} />);
+                layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
     }
     @computed
     get overlayView() {
         return !this.overlayLayout ? (null) :
             (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.OverlayLayout} isSelected={() => false} select={() => { }} />);
+                layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
     }
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 40990b76a..f48be2047 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -35,6 +35,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
     @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
 
+
     CreateBindings(): JsxBindings {
         let bindings: JsxBindings = { ...this.props, };
         for (const key of this.layoutKeys) {
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
index 20a022227..4d7922c1b 100644
--- a/src/client/views/nodes/HistogramBox.tsx
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -150,10 +150,12 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>";
         let xaxislines = this.xaxislines;
         let yaxislines = this.yaxislines;
+        var h = this.props.isTopMost ? this._panelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
+        var w = this.props.isTopMost ? this._panelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
         return (
             <Measure onResize={(r: any) => runInAction(() => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height })}>
                 {({ measureRef }) =>
-                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-this.props.doc.GetNumber(KeyStore.Width, 0) / 2}px, ${-this.props.doc.GetNumber(KeyStore.Height, 0) / 2}px)` }}>
+                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-w / 2}px, ${-h / 2}px)` }}>
                         {xaxislines}
                         {yaxislines}
                         <HistogramBoxPrimitives HistoBox={this} />
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
index 2f4a553b7..a8ada99a4 100644
--- a/src/client/views/nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx
@@ -79,7 +79,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitiv
     }
 
     drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
-        return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler}
+        return <div key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
             style={{
                 position: "absolute",
                 transform: `translate(${r.x}px,${r.y}px)`,
-- 
cgit v1.2.3-70-g09d2


From c3eea60fa586e8ff9cf59497c041044fd0143bb1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 28 Mar 2019 21:03:17 -0400
Subject: organized collectionfreeformview.  fixed issues with showing links

---
 src/client/util/DocumentManager.ts                 |  24 +-
 .../views/collections/CollectionFreeFormView.scss  | 116 -----
 .../views/collections/CollectionFreeFormView.tsx   | 525 ---------------------
 src/client/views/collections/CollectionView.tsx    |   7 +-
 src/client/views/collections/MarqueeView.scss      |   8 -
 src/client/views/collections/MarqueeView.tsx       | 175 -------
 src/client/views/collections/PreviewCursor.scss    |  18 -
 src/client/views/collections/PreviewCursor.tsx     |  76 ---
 .../CollectionFreeFormLinkView.scss                |   6 +
 .../CollectionFreeFormLinkView.tsx                 |  37 ++
 .../CollectionFreeFormLinksView.scss               |   7 +
 .../CollectionFreeFormLinksView.tsx                |  56 +++
 .../collectionFreeForm/CollectionFreeFormView.scss |  95 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 416 ++++++++++++++++
 .../collectionFreeForm/MarqueeView.scss            |   8 +
 .../collections/collectionFreeForm/MarqueeView.tsx | 175 +++++++
 .../collectionFreeForm/PreviewCursor.scss          |  18 +
 .../collectionFreeForm/PreviewCursor.tsx           |  76 +++
 src/client/views/nodes/DocumentContentsView.tsx    |   2 +-
 19 files changed, 924 insertions(+), 921 deletions(-)
 delete mode 100644 src/client/views/collections/CollectionFreeFormView.scss
 delete mode 100644 src/client/views/collections/CollectionFreeFormView.tsx
 delete mode 100644 src/client/views/collections/MarqueeView.scss
 delete mode 100644 src/client/views/collections/MarqueeView.tsx
 delete mode 100644 src/client/views/collections/PreviewCursor.scss
 delete mode 100644 src/client/views/collections/PreviewCursor.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.tsx

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 28624dba8..bf59fbb43 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,10 +1,11 @@
 import React = require('react')
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import { observable, action, computed } from 'mobx';
 import { Document } from "../../fields/Document"
 import { DocumentView } from '../views/nodes/DocumentView';
 import { KeyStore } from '../../fields/KeyStore';
 import { FieldWaiting } from '../../fields/Field';
+import { ListField } from '../../fields/ListField';
 
 
 export class DocumentManager {
@@ -74,4 +75,25 @@ export class DocumentManager {
 
         return (toReturn);
     }
+
+    @computed
+    public get LinkedDocumentViews() {
+        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
+            let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
+            if (linksList && linksList != FieldWaiting && linksList.Data.length) {
+                pairs.push(...linksList.Data.reduce((pairs, link) => {
+                    if (link instanceof Document) {
+                        let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
+                        if (linkToDoc && linkToDoc != FieldWaiting) {
+                            DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+                                pairs.push({ a: dv, b: docView1, l: link })
+                            })
+                        }
+                    }
+                    return pairs;
+                }, [] as { a: DocumentView, b: DocumentView, l: Document }[]));
+            }
+            return pairs;
+        }, [] as { a: DocumentView, b: DocumentView, l: Document }[]);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
deleted file mode 100644
index 7012ce6b9..000000000
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ /dev/null
@@ -1,116 +0,0 @@
-@import "../global_variables";
-
-.collectionfreeformview-linkLine {
-    stroke: black;
-    stroke-width: 3;
-    transform: translate(10000px,10000px);
-    pointer-events: all;
-}
-.collectionfreeformview-svgCanvas{
-  transform: translate(-10000px,-10000px);
-  position: absolute;
-  width: 20000px; 
-  height: 20000px;
-  pointer-events: none;
-}
-.collectionfreeformview-container {
-  .collectionfreeformview > .jsx-parser {
-    position: absolute;
-    height: 100%;
-    width: 100%;
-  }
-
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
-  //nested freeform views
-  // .collectionfreeformview-container {
-    // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
-    //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
-    // background-size: 30px 30px;
-  // }
-
-  box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
-  border: 0px solid $light-color-secondary;
-  border-radius: $border-radius;
-  box-sizing: border-box;
-  position: relative;
-  overflow: hidden;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  .collectionfreeformview {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    .inking-canvas {
-      transform-origin: 50000px 50000px;
-    }
-  }
-}
-.collectionfreeformview-overlay {
-  .collectionfreeformview > .jsx-parser {
-    position: absolute;
-    height: 100%;
-  }
-  .formattedTextBox-cont {
-    background: $light-color-secondary;
-  }
-
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
-
-  opacity: 0.99;
-  border: 0px solid transparent;
-  border-radius: $border-radius;
-  box-sizing: border-box;
-  position:relative;
-  overflow: hidden;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  .collectionfreeformview {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    .inking-canvas {
-      transform-origin: 50000px 50000px;
-    }
-    .formattedTextBox-cont {
-        background:yellow;
-    }
-  }
-}
-
-// selection border...?
-.border {
-  border-style: solid;
-  box-sizing: border-box;
-  width: 98%;
-  height: 98%;
-  border-radius: $border-radius;
-}
-
-//this is an animation for the blinking cursor!
-@keyframes blink {
-  0% {
-    opacity: 0;
-  }
-  49% {
-    opacity: 0;
-  }
-  50% {
-    opacity: 1;
-  }
-}
-
-#prevCursor {
-  animation: blink 1s infinite;
-}
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
deleted file mode 100644
index 44f4eadaa..000000000
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ /dev/null
@@ -1,525 +0,0 @@
-import { action, computed, observable, reaction, runInAction, trace, untracked, IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting, Field, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
-import { DragManager } from "../../util/DragManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { InkingCanvas } from "../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "../nodes/DocumentContentsView";
-import { DocumentViewProps, DocumentView } from "../nodes/DocumentView";
-import "./CollectionFreeFormView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase, CollectionViewProps } from "./CollectionViewBase";
-import { MarqueeView } from "./MarqueeView";
-import { PreviewCursor } from "./PreviewCursor";
-import React = require("react");
-import v5 = require("uuid/v5");
-import { DocumentManager } from "../../util/DocumentManager";
-import { Utils } from "../../../Utils";
-import { Server } from "../../Server";
-import { AverageAggregateParameters } from "../../northstar/model/idea/idea";
-
-@observer
-export class CollectionFreeFormView extends CollectionViewBase {
-    public _canvasRef = React.createRef<HTMLDivElement>();
-    private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
-
-    public addLiveTextBox = (newBox: Document) => {
-        // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
-        this._selectOnLoaded = newBox.Id;
-        //set text to be the typed key and get focus on text box
-        this.props.addDocument(newBox, false);
-        //remove cursor from screen
-        this.PreviewCursorVisible = false;
-    }
-
-    public selectDocuments = (docs: Document[]) => {
-        this.props.CollectionView.SelectedDocs.length = 0;
-        docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
-    }
-
-    public getActiveDocuments = () => {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
-        let active: Document[] = [];
-        if (lvalue && lvalue != FieldWaiting) {
-            lvalue.Data.map(doc => {
-                var page = doc.GetNumber(KeyStore.Page, -1);
-                if (page == curPage || page == -1) {
-                    active.push(doc);
-                }
-            })
-        }
-
-        return active;
-    }
-
-    //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
-    @observable public PreviewCursorVisible: boolean = false;
-    @observable public MarqueeVisible = false;
-    @observable public DownX: number = 0;
-    @observable public DownY: number = 0;
-    @observable private _lastX: number = 0;
-    @observable private _lastY: number = 0;
-
-    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
-    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
-    @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
-    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
-    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
-    @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
-    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
-
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
-            if (de.data instanceof DragManager.DocumentDragData) {
-                let screenX = de.x - (de.data.xOffset as number || 0);
-                let screenY = de.y - (de.data.yOffset as number || 0);
-                const [x, y] = this.getTransform().transformPoint(screenX, screenY);
-                de.data.droppedDocument.SetNumber(KeyStore.X, x);
-                de.data.droppedDocument.SetNumber(KeyStore.Y, y);
-                if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) {
-                    de.data.droppedDocument.SetNumber(KeyStore.Width, 300);
-                    de.data.droppedDocument.SetNumber(KeyStore.Height, 300);
-                }
-                this.bringToFront(de.data.droppedDocument);
-            }
-            return true;
-        }
-        return false;
-    }
-
-
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        this.MarqueeVisible = false;
-    }
-
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        this.PreviewCursorVisible = false;
-        if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-            this._lastX = this.DownX = e.pageX;
-            this._lastY = this.DownY = e.pageY;
-        }
-    }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-
-        if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
-            //show preview text cursor on tap
-            this.PreviewCursorVisible = true;
-            //select is not already selected
-            if (!this.props.isSelected()) {
-                this.props.select(false);
-            }
-        }
-        this.cleanupInteractions();
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble && this.props.active()) {
-            if (e.buttons == 1 && !e.altKey && !e.metaKey) {
-                this.MarqueeVisible = true;
-            }
-            if (this.MarqueeVisible) {
-                e.stopPropagation();
-                e.preventDefault();
-            }
-            else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
-                let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
-                let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
-                let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-                this.SetPan(x - dx, y - dy);
-                this._lastX = e.pageX;
-                this._lastY = e.pageY;
-                e.stopPropagation();
-                e.preventDefault();
-            }
-        }
-    }
-
-    @action
-    onPointerWheel = (e: React.WheelEvent): void => {
-        this.props.select(false);
-        e.stopPropagation();
-        e.preventDefault();
-        let coefficient = 1000;
-
-        if (e.ctrlKey) {
-            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
-            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
-            const coefficient = 1000;
-            let deltaScale = (1 - (e.deltaY / coefficient));
-            this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
-            this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
-            e.stopPropagation();
-            e.preventDefault();
-        } else {
-            // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
-            // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
-            let transform = this.getTransform();
-
-            let deltaScale = (1 - (e.deltaY / coefficient));
-            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
-                deltaScale = 1 / this.zoomScaling;
-            let [x, y] = transform.transformPoint(e.clientX, e.clientY);
-
-            let localTransform = this.getLocalTransform()
-            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
-            // console.log(localTransform)
-
-            this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
-            this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
-        }
-    }
-
-    @action
-    private SetPan(panX: number, panY: number) {
-        var x1 = this.getLocalTransform().inverse().Scale;
-        const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
-        const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
-        this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
-        this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
-    }
-
-    @action
-    onDrop = (e: React.DragEvent): void => {
-        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        super.onDrop(e, { x: pt[0], y: pt[1] });
-    }
-
-    onDragOver = (): void => {
-    }
-
-    @action
-    bringToFront(doc: Document) {
-        const { fieldKey: fieldKey, Document: Document } = this.props;
-
-        const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
-        value.sort((doc1, doc2) => {
-            if (doc1 === doc) {
-                return 1;
-            }
-            if (doc2 === doc) {
-                return -1;
-            }
-            return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
-        }).map((doc, index) => {
-            doc.SetNumber(KeyStore.ZIndex, index + 1)
-        });
-    }
-
-    @computed get backgroundLayout(): string | undefined {
-        let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
-        if (field && field !== FieldWaiting) {
-            return field.Data;
-        }
-    }
-    @computed get overlayLayout(): string | undefined {
-        let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
-        if (field && field !== FieldWaiting) {
-            return field.Data;
-        }
-    }
-
-    focusDocument = (doc: Document) => {
-        let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
-        let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
-        this.SetPan(x, y);
-        this.props.focus(this.props.Document);
-    }
-
-    getDocumentViewProps(document: Document): DocumentViewProps {
-        return {
-            Document: document,
-            AddDocument: this.props.addDocument,
-            RemoveDocument: this.props.removeDocument,
-            ScreenToLocalTransform: this.getTransform,
-            isTopMost: false,
-            SelectOnLoad: document.Id == this._selectOnLoaded,
-            PanelWidth: document.Width,
-            PanelHeight: document.Height,
-            ContentScaling: this.noScaling,
-            ContainingCollectionView: this.props.CollectionView,
-            focus: this.focusDocument
-        }
-    }
-
-    @computed
-    get views() {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
-        if (lvalue && lvalue != FieldWaiting) {
-            return lvalue.Data.map(doc => {
-                if (!doc) return null;
-                var page = doc.GetNumber(KeyStore.Page, 0);
-                return (page != curPage && page != 0) ? (null) :
-                    (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
-            })
-        }
-        return null;
-    }
-
-    @computed
-    get backgroundView() {
-        return !this.backgroundLayout ? (null) :
-            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
-    }
-    @computed
-    get overlayView() {
-        return !this.overlayLayout ? (null) :
-            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
-    }
-
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
-    getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
-    getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
-    noScaling = () => 1;
-
-    //when focus is lost, this will remove the preview cursor
-    @action
-    onBlur = (): void => {
-        this.PreviewCursorVisible = false;
-    }
-
-    private crosshairs?: HTMLCanvasElement;
-    drawCrosshairs = (backgroundColor: string) => {
-        if (this.crosshairs) {
-            let c = this.crosshairs;
-            let ctx = c.getContext('2d');
-            if (ctx) {
-                ctx.fillStyle = backgroundColor;
-                ctx.fillRect(0, 0, 20, 20);
-
-                ctx.fillStyle = "black";
-                ctx.lineWidth = 0.5;
-
-                ctx.beginPath();
-
-                ctx.moveTo(10, 0);
-                ctx.lineTo(10, 8);
-
-                ctx.moveTo(10, 20);
-                ctx.lineTo(10, 12);
-
-                ctx.moveTo(0, 10);
-                ctx.lineTo(8, 10);
-
-                ctx.moveTo(20, 10);
-                ctx.lineTo(12, 10);
-
-                ctx.stroke();
-
-                // ctx.font = "10px Arial";
-                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
-            }
-        }
-    }
-
-    render() {
-        let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
-
-        const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
-        const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
-        // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
-        // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
-        // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
-
-        return (
-            <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
-                onPointerDown={this.onPointerDown}
-                onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
-                onWheel={this.onPointerWheel}
-                onDrop={this.onDrop.bind(this)}
-                onDragOver={this.onDragOver}
-                onBlur={this.onBlur}
-                style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }}
-                tabIndex={0}
-                ref={this.createDropTarget}>
-                <div className="collectionfreeformview"
-                    style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
-                    ref={this._canvasRef}>
-                    {this.backgroundView}
-                    <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
-                    <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
-                    {this.views}
-                    <LinksView {...this.props} />
-                    {super.getCursors().map(entry => {
-                        if (entry.Data.length > 0) {
-                            let id = entry.Data[0][0];
-                            let email = entry.Data[0][1];
-                            let point = entry.Data[1];
-                            this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
-                            return (
-                                <div
-                                    key={id}
-                                    style={{
-                                        position: "absolute",
-                                        transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
-                                        zIndex: 10000,
-                                        transformOrigin: 'center center',
-                                    }}
-                                >
-                                    <canvas
-                                        ref={(el) => { if (el) this.crosshairs = el }}
-                                        width={20}
-                                        height={20}
-                                        style={{
-                                            position: 'absolute',
-                                            width: "20px",
-                                            height: "20px",
-                                            opacity: 0.5,
-                                            borderRadius: "50%",
-                                            border: "2px solid black"
-                                        }}
-                                    />
-                                    <p
-                                        style={{
-                                            fontSize: 14,
-                                            color: "black",
-                                            // fontStyle: "italic",
-                                            marginLeft: -12,
-                                            marginTop: 4
-                                        }}
-                                    >{email[0].toUpperCase()}</p>
-                                </div>
-                            );
-                        }
-                    })}
-                </div>
-                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
-                    addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
-                    getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
-                {this.overlayView}
-
-            </div>
-        );
-    }
-}
-
-@observer
-export class LinksView extends React.Component<CollectionViewProps> {
-    @observable _connections: { a: Document, b: Document, l: Document }[] = [];
-
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(() => DocumentManager.Instance.DocumentViews.map(dv => [
-            dv.props.Document.Get(KeyStore.AnnotationOn, false),
-            dv.props.Document.Get(KeyStore.LinkedFromDocs, false),
-            dv.props.Document.Get(KeyStore.LinkedToDocs, false)
-        ]), () => runInAction(() => this._connections = this.findPairs()), { fireImmediately: true });
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this._reactionDisposer = undefined;
-    }
-
-    filtered(views: DocumentView[]) {
-        return views.filter(sv =>
-            sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document
-        );
-    }
-
-    findPairs() {
-        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
-            untracked(() => {
-                let srcViews = [dv];
-                let srcAnnot = dv.props.Document.GetT(KeyStore.AnnotationOn, Document);
-                if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) {
-                    srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document)
-                }
-                let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
-                if (linksList && linksList != FieldWaiting && linksList.Data.length) {
-                    pairs.push(...linksList.Data.reduce((pairs, link) => {
-                        if (link instanceof Document) {
-                            let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
-                            if (linkToDoc && linkToDoc != FieldWaiting) {
-                                DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                                    let targetViews = [docView1];
-                                    let docAnnot = docView1.props.Document.GetT(KeyStore.AnnotationOn, Document);
-                                    if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) {
-                                        targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document)
-                                    }
-                                    this.filtered(targetViews).map(tv => this.filtered(srcViews).map(sv =>
-                                        pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link })))
-                                })
-                            }
-                        }
-                        return pairs;
-                    }, [] as { a: Document, b: Document, l: Document }[]));
-                }
-            })
-            return pairs;
-        }, [] as { a: Document, b: Document, l: Document }[]);
-    }
-
-    onPointerDown(e: React.PointerEvent) {
-        let line = (e.nativeEvent as any).path[0];
-        line.style.stroke = "red";
-        Server.GetField(line.id, action((f: Opt<Field>) => {
-            if (f instanceof Document) {
-                console.log(f.Title);
-            }
-        }));
-    }
-
-    @computed
-    get uniqueConnections() {
-        return this._connections.reduce((y, t) => {
-            if (!y.reduce((found, yi) => {
-                if (yi.a == t.a && yi.b == t.b) {
-                    if (yi.l != t.l)
-                        yi.n++;
-                    return true;
-                }
-                return found;
-            }, false)) {
-                y.push({ a: t.a, b: t.b, l: t.l, n: 1 });
-            }
-            return y
-        }, [] as { a: Document, b: Document, l: Document, n: number }[]);
-    }
-
-    render() {
-        if (!this._connections.length)
-            return (null);
-        return <svg className="collectionfreeformview-svgCanvas">
-            {this.uniqueConnections.map(c => {
-                let x1 = c.a.GetNumber(KeyStore.X, 0) + c.a.GetNumber(KeyStore.Width, 0) / 2;
-                let y1 = c.a.GetNumber(KeyStore.Y, 0) + c.a.GetNumber(KeyStore.Height, 0) / 2;
-                let x2 = c.b.GetNumber(KeyStore.X, 0) + c.b.GetNumber(KeyStore.Width, 0) / 2;
-                let y2 = c.b.GetNumber(KeyStore.Y, 0) + c.b.GetNumber(KeyStore.Height, 0) / 2;
-                return (
-                    <line key={Utils.GenerateGuid()} id={c.l.Id} className="collectionfreeformview-linkLine" onPointerDown={this.onPointerDown}
-                        style={{ strokeWidth: `${c.n * 5}` }}
-                        x1={`${x1}`} y1={`${y1}`}
-                        x2={`${x2}`} y2={`${y2}`} />
-                )
-            })}
-        </svg>
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c804085df..a1498e0ae 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,7 +7,7 @@ import { ContextMenu } from "../ContextMenu";
 import React = require("react");
 import { KeyStore } from "../../../fields/KeyStore";
 import { NumberField } from "../../../fields/NumberField";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionDockingView } from "./CollectionDockingView";
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionViewProps } from "./CollectionViewBase";
@@ -104,6 +104,11 @@ export class CollectionView extends React.Component<CollectionViewProps> {
                 break;
             }
         }
+        doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
+            if (annotationOn == props.Document) {
+                doc.Set(KeyStore.AnnotationOn, undefined, true);
+            }
+        })
 
         if (index !== -1) {
             value.splice(index, 1)
diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/MarqueeView.scss
deleted file mode 100644
index 6d9a79344..000000000
--- a/src/client/views/collections/MarqueeView.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-
-.marqueeView {
-    border-style: dashed;
-    box-sizing: border-box;
-    position: absolute;
-    border-width: 1px;
-    border-color: black;
-}
\ No newline at end of file
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
deleted file mode 100644
index b48ad9c64..000000000
--- a/src/client/views/collections/MarqueeView.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { Documents } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
-import "./MarqueeView.scss";
-import React = require("react");
-import { InkField, StrokeData } from "../../../fields/InkField";
-import { Utils } from "../../../Utils";
-import { InkingCanvas } from "../InkingCanvas";
-
-interface MarqueeViewProps {
-    getMarqueeTransform: () => Transform;
-    getTransform: () => Transform;
-    container: CollectionFreeFormView;
-    addDocument: (doc: Document, allowDuplicates: false) => boolean;
-    activeDocuments: () => Document[];
-    selectDocuments: (docs: Document[]) => void;
-    removeDocument: (doc: Document) => boolean;
-}
-
-@observer
-export class MarqueeView extends React.Component<MarqueeViewProps>
-{
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    @observable _lastX: number = 0;
-    @observable _lastY: number = 0;
-    @observable _downX: number = 0;
-    @observable _downY: number = 0;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(
-            () => this.props.container.MarqueeVisible,
-            (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY))
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this.cleanupInteractions();
-    }
-
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("pointermove", this.onPointerMove, true)
-        document.removeEventListener("pointerup", this.onPointerUp, true);
-        document.removeEventListener("keydown", this.marqueeCommand, true);
-    }
-
-    @action
-    onPointerDown = (visible: boolean, downX: number, downY: number): void => {
-        if (visible) {
-            this._downX = this._lastX = downX;
-            this._downY = this._lastY = downY;
-            document.addEventListener("pointermove", this.onPointerMove, true)
-            document.addEventListener("pointerup", this.onPointerUp, true);
-            document.addEventListener("keydown", this.marqueeCommand, true);
-        }
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        this._lastX = e.pageX;
-        this._lastY = e.pageY;
-    }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        this.cleanupInteractions();
-        if (!e.shiftKey) {
-            SelectionManager.DeselectAll();
-        }
-        this.props.selectDocuments(this.marqueeSelect());
-    }
-
-    intersectRect(r1: { left: number, top: number, width: number, height: number },
-        r2: { left: number, top: number, width: number, height: number }) {
-        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
-    }
-
-    get Bounds() {
-        let left = this._downX < this._lastX ? this._downX : this._lastX;
-        let top = this._downY < this._lastY ? this._downY : this._lastY;
-        let topLeft = this.props.getTransform().transformPoint(left, top);
-        let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
-    }
-
-    @action
-    marqueeCommand = (e: KeyboardEvent) => {
-        if (e.key == "Backspace" || e.key == "Delete") {
-            this.marqueeSelect().map(d => this.props.removeDocument(d));
-            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
-            this.cleanupInteractions();
-        }
-        if (e.key == "c") {
-            let bounds = this.Bounds;
-            let selected = this.marqueeSelect().map(d => {
-                this.props.removeDocument(d);
-                d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
-                d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
-                d.SetNumber(KeyStore.Page, 0);
-                d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
-                return d;
-            });
-            let liftedInk = this.marqueeInkSelect(true);
-            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
-            //setTimeout(() => {
-            let newCollection = Documents.FreeformDocument(selected, {
-                x: bounds.left,
-                y: bounds.top,
-                panx: 0,
-                pany: 0,
-                width: bounds.width,
-                height: bounds.height,
-                backgroundColor: "Transparent",
-                ink: liftedInk,
-                title: "a nested collection"
-            });
-            this.props.addDocument(newCollection, false);
-            // }, 100);
-            this.cleanupInteractions();
-        }
-    }
-    marqueeInkSelect(select: boolean) {
-        let selRect = this.Bounds;
-        let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
-        let centerShiftY = 0 - (selRect.top + selRect.height / 2);
-        let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-        if (ink && ink != FieldWaiting && ink.Data) {
-            let idata = new Map();
-            ink.Data.forEach((value: StrokeData, key: string, map: any) => {
-                let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
-                if (inside && select) {
-                    idata.set(key,
-                        {
-                            pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
-                            color: value.color,
-                            width: value.width,
-                            tool: value.tool,
-                            page: -1
-                        });
-                } else if (!inside && !select) {
-                    idata.set(key, value);
-                }
-            })
-            return idata;
-        }
-    }
-
-    marqueeSelect() {
-        let selRect = this.Bounds;
-        let selection: Document[] = [];
-        this.props.activeDocuments().map(doc => {
-            var x = doc.GetNumber(KeyStore.X, 0);
-            var y = doc.GetNumber(KeyStore.Y, 0);
-            var w = doc.GetNumber(KeyStore.Width, 0);
-            var h = doc.GetNumber(KeyStore.Height, 0);
-            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
-                selection.push(doc)
-        })
-        return selection;
-    }
-
-    render() {
-        let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
-        let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.scss b/src/client/views/collections/PreviewCursor.scss
deleted file mode 100644
index a797411f6..000000000
--- a/src/client/views/collections/PreviewCursor.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-
-.previewCursor {
-    color: black;
-    position: absolute;
-    transform-origin: left top;
-    pointer-events: none;
-}
-
-//this is an animation for the blinking cursor!
-@keyframes blink {
-	0% {opacity: 0}
-	49%{opacity: 0}
-	50% {opacity: 1}
-}
-
-#previewCursor {
-    animation: blink 1s infinite;
-}
\ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
deleted file mode 100644
index 8ae59a83a..000000000
--- a/src/client/views/collections/PreviewCursor.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { Opt } from "../../../fields/Field";
-import { Documents } from "../../documents/Documents";
-import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
-import "./PreviewCursor.scss";
-import React = require("react");
-
-
-export interface PreviewCursorProps {
-    getTransform: () => Transform;
-    container: CollectionFreeFormView;
-    addLiveTextDocument: (doc: Document) => void;
-}
-
-@observer
-export class PreviewCursor extends React.Component<PreviewCursorProps>  {
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    @observable _lastX: number = 0;
-    @observable _lastY: number = 0;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(
-            () => this.props.container.PreviewCursorVisible,
-            (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this.cleanupInteractions();
-    }
-
-
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("keypress", this.onKeyPress, false);
-    }
-
-    @action
-    onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
-        if (visible) {
-            document.addEventListener("keypress", this.onKeyPress, false);
-            this._lastX = downX;
-            this._lastY = downY;
-        } else
-            this.cleanupInteractions();
-    }
-
-    @action
-    onKeyPress = (e: KeyboardEvent) => {
-        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
-        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
-        // the keyPress here.
-        //if not these keys, make a textbox if preview cursor is active!
-        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
-            //make textbox and add it to this collection
-            let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
-            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
-            this.props.addLiveTextDocument(newBox);
-            e.stopPropagation();
-        }
-    }
-
-    render() {
-        //get local position and place cursor there!
-        let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
-        return (
-            !this.props.container.PreviewCursorVisible ? (null) :
-                <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
-
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
new file mode 100644
index 000000000..3b2f79be1
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -0,0 +1,6 @@
+.collectionfreeformlinkview-linkLine {
+    stroke: black;
+    stroke-width: 3;
+    transform: translate(10000px,10000px);
+    pointer-events: all;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
new file mode 100644
index 000000000..e84f0c5ad
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -0,0 +1,37 @@
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Utils } from "../../../../Utils";
+import "./CollectionFreeFormLinkView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+
+export interface CollectionFreeFormLinkViewProps {
+    A: Document;
+    B: Document;
+    LinkDocs: Document[];
+}
+
+@observer
+export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
+
+    onPointerDown = (e: React.PointerEvent) => {
+        this.props.LinkDocs.map(l =>
+            console.log("Link:" + l.Title));
+    }
+    render() {
+        let l = this.props.LinkDocs;
+        let a = this.props.A;
+        let b = this.props.B;
+        let x1 = a.GetNumber(KeyStore.X, 0) + a.GetNumber(KeyStore.Width, 0) / 2;
+        let y1 = a.GetNumber(KeyStore.Y, 0) + a.GetNumber(KeyStore.Height, 0) / 2;
+        let x2 = b.GetNumber(KeyStore.X, 0) + b.GetNumber(KeyStore.Width, 0) / 2;
+        let y2 = b.GetNumber(KeyStore.Y, 0) + b.GetNumber(KeyStore.Height, 0) / 2;
+        return (
+            <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
+                style={{ strokeWidth: `${l.length * 5}` }}
+                x1={`${x1}`} y1={`${y1}`}
+                x2={`${x2}`} y2={`${y2}`} />
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
new file mode 100644
index 000000000..9af0df7f5
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -0,0 +1,7 @@
+.collectionfreeformlinksview-svgCanvas{
+    transform: translate(-10000px,-10000px);
+    position: absolute;
+    width: 20000px; 
+    height: 20000px;
+    pointer-events: none;
+  }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
new file mode 100644
index 000000000..4f28f43eb
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -0,0 +1,56 @@
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Utils } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DocumentView } from "../../nodes/DocumentView";
+import { CollectionViewProps } from "../CollectionViewBase";
+import "./CollectionFreeFormLinksView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+
+@observer
+export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
+
+    documentAnchors(view: DocumentView) {
+        let equalViews = [view];
+        let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
+        if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) {
+            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document)
+        }
+        return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document);
+    }
+
+    @computed
+    get uniqueConnections() {
+        return DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
+            let srcViews = this.documentAnchors(connection.a);
+            let targetViews = this.documentAnchors(connection.b);
+            let possiblePairs: { a: Document, b: Document, }[] = [];
+            srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
+            possiblePairs.map(possiblePair => {
+                if (!drawnPairs.reduce((found, drawnPair) => {
+                    let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b);
+                    if (match) {
+                        if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false))
+                            drawnPair.l.push(connection.l);
+                    }
+                    return match || found;
+                }, false)) {
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] });
+                }
+            })
+            return drawnPairs
+        }, [] as { a: Document, b: Document, l: Document[] }[]);
+    }
+
+    render() {
+        return (
+            <svg className="collectionfreeformlinksview-svgCanvas">
+                {this.uniqueConnections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />)}
+            </svg>);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
new file mode 100644
index 000000000..9c5e98005
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -0,0 +1,95 @@
+@import "../../global_variables";
+
+.collectionfreeformview-container {
+  .collectionfreeformview > .jsx-parser {
+    position: absolute;
+    height: 100%;
+    width: 100%;
+  }
+
+  .inking-canvas {
+    transform-origin: 50000px 50000px;
+  }
+  //nested freeform views
+  // .collectionfreeformview-container {
+    // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
+    //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
+    // background-size: 30px 30px;
+  // }
+
+  box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+  border: 0px solid $light-color-secondary;
+  border-radius: $border-radius;
+  box-sizing: border-box;
+  position: relative;
+  overflow: hidden;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  .collectionfreeformview {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    .inking-canvas {
+      transform-origin: 50000px 50000px;
+    }
+  }
+}
+.collectionfreeformview-overlay {
+  .collectionfreeformview > .jsx-parser {
+    position: absolute;
+    height: 100%;
+  }
+  .formattedTextBox-cont {
+    background: $light-color-secondary;
+  }
+
+  .inking-canvas {
+    transform-origin: 50000px 50000px;
+  }
+
+  opacity: 0.99;
+  border: 0px solid transparent;
+  border-radius: $border-radius;
+  box-sizing: border-box;
+  position:relative;
+  overflow: hidden;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  .collectionfreeformview {
+    .formattedTextBox-cont {
+        background:yellow;
+    }
+  }
+}
+
+// selection border...?
+.border {
+  border-style: solid;
+  box-sizing: border-box;
+  width: 98%;
+  height: 98%;
+  border-radius: $border-radius;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+  0% {
+    opacity: 0;
+  }
+  49% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+}
+
+#prevCursor {
+  animation: blink 1s infinite;
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
new file mode 100644
index 000000000..1f90b0d46
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -0,0 +1,416 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { ListField } from "../../../../fields/ListField";
+import { TextField } from "../../../../fields/TextField";
+import { DragManager } from "../../../util/DragManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { InkingCanvas } from "../../InkingCanvas";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocumentContentsView } from "../../nodes/DocumentContentsView";
+import { DocumentViewProps } from "../../nodes/DocumentView";
+import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
+import { CollectionViewBase } from "../CollectionViewBase";
+import { MarqueeView } from "./MarqueeView";
+import { PreviewCursor } from "./PreviewCursor";
+import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import "./CollectionFreeFormView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+
+@observer
+export class CollectionFreeFormView extends CollectionViewBase {
+    public _canvasRef = React.createRef<HTMLDivElement>();
+    private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
+
+    public addLiveTextBox = (newBox: Document) => {
+        // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
+        this._selectOnLoaded = newBox.Id;
+        //set text to be the typed key and get focus on text box
+        this.props.addDocument(newBox, false);
+        //remove cursor from screen
+        this.PreviewCursorVisible = false;
+    }
+
+    public selectDocuments = (docs: Document[]) => {
+        this.props.CollectionView.SelectedDocs.length = 0;
+        docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+    }
+
+    public getActiveDocuments = () => {
+        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
+        let active: Document[] = [];
+        if (lvalue && lvalue != FieldWaiting) {
+            lvalue.Data.map(doc => {
+                var page = doc.GetNumber(KeyStore.Page, -1);
+                if (page == curPage || page == -1) {
+                    active.push(doc);
+                }
+            })
+        }
+
+        return active;
+    }
+
+    //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
+    @observable public PreviewCursorVisible: boolean = false;
+    @observable public MarqueeVisible = false;
+    @observable public DownX: number = 0;
+    @observable public DownY: number = 0;
+    @observable private _lastX: number = 0;
+    @observable private _lastY: number = 0;
+
+    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
+    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
+    @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
+    @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+    @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
+    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
+    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
+
+    @undoBatch
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.drop(e, de)) {
+            if (de.data instanceof DragManager.DocumentDragData) {
+                let screenX = de.x - (de.data.xOffset as number || 0);
+                let screenY = de.y - (de.data.yOffset as number || 0);
+                const [x, y] = this.getTransform().transformPoint(screenX, screenY);
+                de.data.droppedDocument.SetNumber(KeyStore.X, x);
+                de.data.droppedDocument.SetNumber(KeyStore.Y, y);
+                if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) {
+                    de.data.droppedDocument.SetNumber(KeyStore.Width, 300);
+                    de.data.droppedDocument.SetNumber(KeyStore.Height, 300);
+                }
+                this.bringToFront(de.data.droppedDocument);
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        this.MarqueeVisible = false;
+    }
+
+    @action
+    onPointerDown = (e: React.PointerEvent): void => {
+        this.PreviewCursorVisible = false;
+        if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
+            this._lastX = this.DownX = e.pageX;
+            this._lastY = this.DownY = e.pageY;
+        }
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        e.stopPropagation();
+
+        if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
+            //show preview text cursor on tap
+            this.PreviewCursorVisible = true;
+            //select is not already selected
+            if (!this.props.isSelected()) {
+                this.props.select(false);
+            }
+        }
+        this.cleanupInteractions();
+    }
+
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        if (!e.cancelBubble && this.props.active()) {
+            if (e.buttons == 1 && !e.altKey && !e.metaKey) {
+                this.MarqueeVisible = true;
+            }
+            if (this.MarqueeVisible) {
+                e.stopPropagation();
+                e.preventDefault();
+            }
+            else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
+                let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
+                let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
+                let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+                this.SetPan(x - dx, y - dy);
+                this._lastX = e.pageX;
+                this._lastY = e.pageY;
+                e.stopPropagation();
+                e.preventDefault();
+            }
+        }
+    }
+
+    @action
+    onPointerWheel = (e: React.WheelEvent): void => {
+        this.props.select(false);
+        e.stopPropagation();
+        e.preventDefault();
+        let coefficient = 1000;
+
+        if (e.ctrlKey) {
+            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+            const coefficient = 1000;
+            let deltaScale = (1 - (e.deltaY / coefficient));
+            this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
+            this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
+            e.stopPropagation();
+            e.preventDefault();
+        } else {
+            // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
+            // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
+            let transform = this.getTransform();
+
+            let deltaScale = (1 - (e.deltaY / coefficient));
+            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
+                deltaScale = 1 / this.zoomScaling;
+            let [x, y] = transform.transformPoint(e.clientX, e.clientY);
+
+            let localTransform = this.getLocalTransform()
+            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+            // console.log(localTransform)
+
+            this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
+            this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+        }
+    }
+
+    @action
+    private SetPan(panX: number, panY: number) {
+        var x1 = this.getLocalTransform().inverse().Scale;
+        const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
+        const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
+        this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
+        this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
+    }
+
+    @action
+    onDrop = (e: React.DragEvent): void => {
+        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+        super.onDrop(e, { x: pt[0], y: pt[1] });
+    }
+
+    onDragOver = (): void => {
+    }
+
+    @action
+    bringToFront(doc: Document) {
+        const { fieldKey: fieldKey, Document: Document } = this.props;
+
+        const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
+        value.sort((doc1, doc2) => {
+            if (doc1 === doc) {
+                return 1;
+            }
+            if (doc2 === doc) {
+                return -1;
+            }
+            return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
+        }).map((doc, index) => {
+            doc.SetNumber(KeyStore.ZIndex, index + 1)
+        });
+    }
+
+    @computed get backgroundLayout(): string | undefined {
+        let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
+        if (field && field !== FieldWaiting) {
+            return field.Data;
+        }
+    }
+    @computed get overlayLayout(): string | undefined {
+        let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
+        if (field && field !== FieldWaiting) {
+            return field.Data;
+        }
+    }
+
+    focusDocument = (doc: Document) => {
+        let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
+        let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
+        this.SetPan(x, y);
+        this.props.focus(this.props.Document);
+    }
+
+    getDocumentViewProps(document: Document): DocumentViewProps {
+        return {
+            Document: document,
+            AddDocument: this.props.addDocument,
+            RemoveDocument: this.props.removeDocument,
+            ScreenToLocalTransform: this.getTransform,
+            isTopMost: false,
+            SelectOnLoad: document.Id == this._selectOnLoaded,
+            PanelWidth: document.Width,
+            PanelHeight: document.Height,
+            ContentScaling: this.noScaling,
+            ContainingCollectionView: this.props.CollectionView,
+            focus: this.focusDocument
+        }
+    }
+
+    @computed
+    get views() {
+        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
+        if (lvalue && lvalue != FieldWaiting) {
+            return lvalue.Data.map(doc => {
+                if (!doc) return null;
+                var page = doc.GetNumber(KeyStore.Page, 0);
+                return (page != curPage && page != 0) ? (null) :
+                    (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+            })
+        }
+        return null;
+    }
+
+    @computed
+    get backgroundView() {
+        return !this.backgroundLayout ? (null) :
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
+                layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+    }
+    @computed
+    get overlayView() {
+        return !this.overlayLayout ? (null) :
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
+                layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+    }
+
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
+    getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
+    getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
+    noScaling = () => 1;
+
+    //when focus is lost, this will remove the preview cursor
+    @action
+    onBlur = (): void => {
+        this.PreviewCursorVisible = false;
+    }
+
+    private crosshairs?: HTMLCanvasElement;
+    drawCrosshairs = (backgroundColor: string) => {
+        if (this.crosshairs) {
+            let c = this.crosshairs;
+            let ctx = c.getContext('2d');
+            if (ctx) {
+                ctx.fillStyle = backgroundColor;
+                ctx.fillRect(0, 0, 20, 20);
+
+                ctx.fillStyle = "black";
+                ctx.lineWidth = 0.5;
+
+                ctx.beginPath();
+
+                ctx.moveTo(10, 0);
+                ctx.lineTo(10, 8);
+
+                ctx.moveTo(10, 20);
+                ctx.lineTo(10, 12);
+
+                ctx.moveTo(0, 10);
+                ctx.lineTo(8, 10);
+
+                ctx.moveTo(20, 10);
+                ctx.lineTo(12, 10);
+
+                ctx.stroke();
+
+                // ctx.font = "10px Arial";
+                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+            }
+        }
+    }
+
+    render() {
+        let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
+
+        const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
+        const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
+        // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
+        // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
+        // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
+
+        return (
+            <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
+                onPointerDown={this.onPointerDown}
+                onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
+                onWheel={this.onPointerWheel}
+                onDrop={this.onDrop.bind(this)}
+                onDragOver={this.onDragOver}
+                onBlur={this.onBlur}
+                style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }}
+                tabIndex={0}
+                ref={this.createDropTarget}>
+                <div className="collectionfreeformview"
+                    style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
+                    ref={this._canvasRef}>
+                    {this.backgroundView}
+                    <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
+                    <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
+                    {this.views}
+                    <CollectionFreeFormLinksView {...this.props} />
+                    {super.getCursors().map(entry => {
+                        if (entry.Data.length > 0) {
+                            let id = entry.Data[0][0];
+                            let email = entry.Data[0][1];
+                            let point = entry.Data[1];
+                            this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+                            return (
+                                <div
+                                    key={id}
+                                    style={{
+                                        position: "absolute",
+                                        transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
+                                        zIndex: 10000,
+                                        transformOrigin: 'center center',
+                                    }}
+                                >
+                                    <canvas
+                                        ref={(el) => { if (el) this.crosshairs = el }}
+                                        width={20}
+                                        height={20}
+                                        style={{
+                                            position: 'absolute',
+                                            width: "20px",
+                                            height: "20px",
+                                            opacity: 0.5,
+                                            borderRadius: "50%",
+                                            border: "2px solid black"
+                                        }}
+                                    />
+                                    <p
+                                        style={{
+                                            fontSize: 14,
+                                            color: "black",
+                                            // fontStyle: "italic",
+                                            marginLeft: -12,
+                                            marginTop: 4
+                                        }}
+                                    >{email[0].toUpperCase()}</p>
+                                </div>
+                            );
+                        }
+                    })}
+                </div>
+                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+                    addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+                    getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
+                {this.overlayView}
+
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
new file mode 100644
index 000000000..6d9a79344
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -0,0 +1,8 @@
+
+.marqueeView {
+    border-style: dashed;
+    box-sizing: border-box;
+    position: absolute;
+    border-width: 1px;
+    border-color: black;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
new file mode 100644
index 000000000..2692690dd
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -0,0 +1,175 @@
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting, Opt } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Documents } from "../../../documents/Documents";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { Transform } from "../../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./MarqueeView.scss";
+import React = require("react");
+import { InkField, StrokeData } from "../../../../fields/InkField";
+import { Utils } from "../../../../Utils";
+import { InkingCanvas } from "../../InkingCanvas";
+
+interface MarqueeViewProps {
+    getMarqueeTransform: () => Transform;
+    getTransform: () => Transform;
+    container: CollectionFreeFormView;
+    addDocument: (doc: Document, allowDuplicates: false) => boolean;
+    activeDocuments: () => Document[];
+    selectDocuments: (docs: Document[]) => void;
+    removeDocument: (doc: Document) => boolean;
+}
+
+@observer
+export class MarqueeView extends React.Component<MarqueeViewProps>
+{
+    private _reactionDisposer: Opt<IReactionDisposer>;
+
+    @observable _lastX: number = 0;
+    @observable _lastY: number = 0;
+    @observable _downX: number = 0;
+    @observable _downY: number = 0;
+
+    componentDidMount() {
+        this._reactionDisposer = reaction(
+            () => this.props.container.MarqueeVisible,
+            (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY))
+    }
+    componentWillUnmount() {
+        if (this._reactionDisposer) {
+            this._reactionDisposer();
+        }
+        this.cleanupInteractions();
+    }
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("pointermove", this.onPointerMove, true)
+        document.removeEventListener("pointerup", this.onPointerUp, true);
+        document.removeEventListener("keydown", this.marqueeCommand, true);
+    }
+
+    @action
+    onPointerDown = (visible: boolean, downX: number, downY: number): void => {
+        if (visible) {
+            this._downX = this._lastX = downX;
+            this._downY = this._lastY = downY;
+            document.addEventListener("pointermove", this.onPointerMove, true)
+            document.addEventListener("pointerup", this.onPointerUp, true);
+            document.addEventListener("keydown", this.marqueeCommand, true);
+        }
+    }
+
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        this._lastX = e.pageX;
+        this._lastY = e.pageY;
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        this.cleanupInteractions();
+        if (!e.shiftKey) {
+            SelectionManager.DeselectAll();
+        }
+        this.props.selectDocuments(this.marqueeSelect());
+    }
+
+    intersectRect(r1: { left: number, top: number, width: number, height: number },
+        r2: { left: number, top: number, width: number, height: number }) {
+        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+    }
+
+    get Bounds() {
+        let left = this._downX < this._lastX ? this._downX : this._lastX;
+        let top = this._downY < this._lastY ? this._downY : this._lastY;
+        let topLeft = this.props.getTransform().transformPoint(left, top);
+        let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+    }
+
+    @action
+    marqueeCommand = (e: KeyboardEvent) => {
+        if (e.key == "Backspace" || e.key == "Delete") {
+            this.marqueeSelect().map(d => this.props.removeDocument(d));
+            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+            this.cleanupInteractions();
+        }
+        if (e.key == "c") {
+            let bounds = this.Bounds;
+            let selected = this.marqueeSelect().map(d => {
+                this.props.removeDocument(d);
+                d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
+                d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
+                d.SetNumber(KeyStore.Page, 0);
+                d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
+                return d;
+            });
+            let liftedInk = this.marqueeInkSelect(true);
+            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+            //setTimeout(() => {
+            let newCollection = Documents.FreeformDocument(selected, {
+                x: bounds.left,
+                y: bounds.top,
+                panx: 0,
+                pany: 0,
+                width: bounds.width,
+                height: bounds.height,
+                backgroundColor: "Transparent",
+                ink: liftedInk,
+                title: "a nested collection"
+            });
+            this.props.addDocument(newCollection, false);
+            // }, 100);
+            this.cleanupInteractions();
+        }
+    }
+    marqueeInkSelect(select: boolean) {
+        let selRect = this.Bounds;
+        let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+        let centerShiftY = 0 - (selRect.top + selRect.height / 2);
+        let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+        if (ink && ink != FieldWaiting && ink.Data) {
+            let idata = new Map();
+            ink.Data.forEach((value: StrokeData, key: string, map: any) => {
+                let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
+                if (inside && select) {
+                    idata.set(key,
+                        {
+                            pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
+                            color: value.color,
+                            width: value.width,
+                            tool: value.tool,
+                            page: -1
+                        });
+                } else if (!inside && !select) {
+                    idata.set(key, value);
+                }
+            })
+            return idata;
+        }
+    }
+
+    marqueeSelect() {
+        let selRect = this.Bounds;
+        let selection: Document[] = [];
+        this.props.activeDocuments().map(doc => {
+            var x = doc.GetNumber(KeyStore.X, 0);
+            var y = doc.GetNumber(KeyStore.Y, 0);
+            var w = doc.GetNumber(KeyStore.Width, 0);
+            var h = doc.GetNumber(KeyStore.Height, 0);
+            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
+                selection.push(doc)
+        })
+        return selection;
+    }
+
+    render() {
+        let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+        let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+        return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
new file mode 100644
index 000000000..a797411f6
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
@@ -0,0 +1,18 @@
+
+.previewCursor {
+    color: black;
+    position: absolute;
+    transform-origin: left top;
+    pointer-events: none;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+	0% {opacity: 0}
+	49%{opacity: 0}
+	50% {opacity: 1}
+}
+
+#previewCursor {
+    animation: blink 1s infinite;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
new file mode 100644
index 000000000..95364f04b
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -0,0 +1,76 @@
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { Opt } from "../../../../fields/Field";
+import { Documents } from "../../../documents/Documents";
+import { Transform } from "../../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./PreviewCursor.scss";
+import React = require("react");
+
+
+export interface PreviewCursorProps {
+    getTransform: () => Transform;
+    container: CollectionFreeFormView;
+    addLiveTextDocument: (doc: Document) => void;
+}
+
+@observer
+export class PreviewCursor extends React.Component<PreviewCursorProps>  {
+    private _reactionDisposer: Opt<IReactionDisposer>;
+
+    @observable _lastX: number = 0;
+    @observable _lastY: number = 0;
+
+    componentDidMount() {
+        this._reactionDisposer = reaction(
+            () => this.props.container.PreviewCursorVisible,
+            (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
+    }
+    componentWillUnmount() {
+        if (this._reactionDisposer) {
+            this._reactionDisposer();
+        }
+        this.cleanupInteractions();
+    }
+
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("keypress", this.onKeyPress, false);
+    }
+
+    @action
+    onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
+        if (visible) {
+            document.addEventListener("keypress", this.onKeyPress, false);
+            this._lastX = downX;
+            this._lastY = downY;
+        } else
+            this.cleanupInteractions();
+    }
+
+    @action
+    onKeyPress = (e: KeyboardEvent) => {
+        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
+        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+        // the keyPress here.
+        //if not these keys, make a textbox if preview cursor is active!
+        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+            //make textbox and add it to this collection
+            let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
+            this.props.addLiveTextDocument(newBox);
+            e.stopPropagation();
+        }
+    }
+
+    render() {
+        //get local position and place cursor there!
+        let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+        return (
+            !this.props.container.PreviewCursorVisible ? (null) :
+                <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
+
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f48be2047..b54744337 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -5,7 +5,7 @@ import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
-- 
cgit v1.2.3-70-g09d2


From 1bd678851632bbbb302363574eb5e3e19dc343e9 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 13:18:35 -0400
Subject: reorganized and mostly working northstar histograms.

---
 src/client/documents/Documents.ts                  |  13 +-
 src/client/northstar/core/filter/FilterModel.ts    |   2 +-
 src/client/northstar/dash-fields/HistogramField.ts |  64 ++++
 src/client/northstar/dash-nodes/HistogramBox.scss  |  34 ++
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 161 ++++++++++
 .../dash-nodes/HistogramBoxPrimitives.scss         |  26 ++
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 341 +++++++++++++++++++++
 .../dash-nodes/HistogramLabelPrimitives.scss       |  13 +
 .../dash-nodes/HistogramLabelPrimitives.tsx        |  78 +++++
 src/client/northstar/model/ModelHelpers.ts         |  18 +-
 .../model/binRanges/VisualBinRangeHelper.ts        |   6 +-
 src/client/northstar/operations/BaseOperation.ts   |   4 +-
 .../northstar/operations/HistogramOperation.ts     |  13 +-
 src/client/northstar/utils/SizeConverter.ts        |   6 +-
 src/client/views/Main.tsx                          |  39 +--
 src/client/views/nodes/DocumentContentsView.tsx    |   5 +-
 src/client/views/nodes/DocumentView.tsx            |   3 -
 src/client/views/nodes/HistogramBox.scss           |  22 --
 src/client/views/nodes/HistogramBox.tsx            | 105 -------
 src/client/views/nodes/HistogramBoxPrimitives.scss |  25 --
 src/client/views/nodes/HistogramBoxPrimitives.tsx  | 336 --------------------
 .../views/nodes/HistogramLabelPrimitives.scss      |  12 -
 .../views/nodes/HistogramLabelPrimitives.tsx       |  78 -----
 src/fields/HistogramField.ts                       |  59 ----
 src/fields/KeyStore.ts                             |   1 -
 src/server/ServerUtil.ts                           |   3 +-
 .../authentication/models/current_user_utils.ts    |  27 +-
 27 files changed, 789 insertions(+), 705 deletions(-)
 create mode 100644 src/client/northstar/dash-fields/HistogramField.ts
 create mode 100644 src/client/northstar/dash-nodes/HistogramBox.scss
 create mode 100644 src/client/northstar/dash-nodes/HistogramBox.tsx
 create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
 create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
 create mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
 create mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
 delete mode 100644 src/client/views/nodes/HistogramBox.scss
 delete mode 100644 src/client/views/nodes/HistogramBox.tsx
 delete mode 100644 src/client/views/nodes/HistogramBoxPrimitives.scss
 delete mode 100644 src/client/views/nodes/HistogramBoxPrimitives.tsx
 delete mode 100644 src/client/views/nodes/HistogramLabelPrimitives.scss
 delete mode 100644 src/client/views/nodes/HistogramLabelPrimitives.tsx
 delete mode 100644 src/fields/HistogramField.ts

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 837dfe815..663ccae61 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,6 @@
 import { AudioField } from "../../fields/AudioField";
 import { Document } from "../../fields/Document";
-import { Field, FieldWaiting } from "../../fields/Field";
+import { Field } from "../../fields/Field";
 import { HtmlField } from "../../fields/HtmlField";
 import { ImageField } from "../../fields/ImageField";
 import { InkField, StrokeData } from "../../fields/InkField";
@@ -11,6 +11,9 @@ import { PDFField } from "../../fields/PDFField";
 import { TextField } from "../../fields/TextField";
 import { VideoField } from "../../fields/VideoField";
 import { WebField } from "../../fields/WebField";
+import { HistogramField } from "../northstar/dash-fields/HistogramField";
+import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
+import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 import { Server } from "../Server";
 import { CollectionPDFView } from "../views/collections/CollectionPDFView";
 import { CollectionVideoView } from "../views/collections/CollectionVideoView";
@@ -22,10 +25,6 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
 import { VideoBox } from "../views/nodes/VideoBox";
 import { WebBox } from "../views/nodes/WebBox";
-import { HistogramBox } from "../views/nodes/HistogramBox";
-import { FieldView } from "../views/nodes/FieldView";
-import { HistogramField } from "../../fields/HistogramField";
-import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 
 export interface DocumentOptions {
     x?: number;
@@ -44,7 +43,6 @@ export interface DocumentOptions {
     layoutKeys?: Key[];
     viewType?: number;
     backgroundColor?: string;
-    northstarSchema?: string;
 }
 
 export namespace Documents {
@@ -88,7 +86,6 @@ export namespace Documents {
         if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
         if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
         if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
-        if (options.northstarSchema !== undefined) { doc.SetText(KeyStore.NorthstarSchema, options.northstarSchema); }
         return doc;
     }
 
@@ -126,7 +123,7 @@ export namespace Documents {
     function GetHistogramPrototype(): Document {
         if (!histoProto) {
             histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"),
-                { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+                { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
             histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
         }
         return histoProto;
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index bc7938947..aee99d2b6 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -2,10 +2,10 @@ import { ValueComparison } from "./ValueComparision";
 import { Utils } from "../../utils/Utils";
 import { IBaseFilterProvider } from "./IBaseFilterProvider";
 import { FilterOperand } from "./FilterOperand";
-import { HistogramField } from "../../../../fields/HistogramField";
 import { KeyStore } from "../../../../fields/KeyStore";
 import { FieldWaiting } from "../../../../fields/Field";
 import { Document } from "../../../../fields/Document";
+import { HistogramField } from "../../dash-fields/HistogramField";
 
 export class FilterModel {
     public ValueComparisons: ValueComparison[];
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
new file mode 100644
index 000000000..00912c595
--- /dev/null
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -0,0 +1,64 @@
+import { BasicField } from "../../../fields/BasicField";
+import { Field, FieldId } from "../../../fields/Field";
+import { Types } from "../../../server/Message";
+import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
+import { action } from "mobx";
+import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
+import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { Schema } from "../model/idea/idea";
+
+
+export class HistogramField extends BasicField<HistogramOperation> {
+    constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) {
+        super(data ? data : HistogramOperation.Empty, save, id);
+    }
+
+    toString(): string {
+        return JSON.stringify(this.Data);
+    }
+
+    Copy(): Field {
+        return new HistogramField(this.Data);
+    }
+
+    ToScriptString(): string {
+        return `new HistogramField("${this.Data}")`;
+    }
+
+    ToJson(): { type: Types, data: string, _id: string } {
+        return {
+            type: Types.HistogramOp,
+            data: JSON.stringify(this.Data),
+            _id: this.Id
+        }
+    }
+
+    @action
+    static FromJson(id: string, data: any): HistogramField {
+        let jp = JSON.parse(data);
+        let X: AttributeTransformationModel | undefined;
+        let Y: AttributeTransformationModel | undefined;
+        let V: AttributeTransformationModel | undefined;
+
+        let 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 HistogramField(new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization), id, false);
+            }
+        }
+        return new HistogramField(HistogramOperation.Empty, id, false);
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
new file mode 100644
index 000000000..b11840a65
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -0,0 +1,34 @@
+.histogrambox-container {
+    padding: 0vw;
+    position: absolute;
+    text-align: center;
+    width: 100%;
+    height: 100%;
+    background: black;
+  }
+  .histogrambox-xaxislabel {
+    position:absolute;
+    width:100%;
+    text-align: center;
+    bottom:0;
+    background: lightgray;
+    font-size: 14;
+    font-weight: bold;
+  }
+  .histogrambox-yaxislabel {
+    position:absolute;
+    height:100%;
+    width: 25px;
+    bottom:0;
+    background: lightgray;
+  }
+  .histogrambox-yaxislabel-text {
+    position:absolute;
+    transform-origin: left;
+    transform: rotate(-90deg);
+    text-align: center;
+    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
new file mode 100644
index 000000000..9f8c2cfd0
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -0,0 +1,161 @@
+import React = require("react")
+import { action, computed, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import Measure from "react-measure";
+import { Dictionary } from "typescript-collections";
+import { FieldWaiting, Opt } from "../../../fields/Field";
+import { KeyStore } from "../../../fields/KeyStore";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { FilterModel } from '../../northstar/core/filter/FilterModel';
+import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
+import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
+import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
+import { PIXIRectangle } from "../../northstar/utils/MathUtil";
+import { SizeConverter } from "../../northstar/utils/SizeConverter";
+import { DragManager } from "../../util/DragManager";
+import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
+import { HistogramField } from "../dash-fields/HistogramField";
+import "../utils/Extensions"
+import "./HistogramBox.scss";
+import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
+import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
+
+export interface HistogramPrimitivesProps {
+    HistoBox: HistogramBox;
+}
+
+@observer
+export class HistogramBox extends React.Component<FieldViewProps> {
+    public static LayoutString(fieldStr: string = "DataKey") { 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 PanelWidth: number = 100;
+    @observable public PanelHeight: number = 100;
+    @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
+    @observable public VisualBinRanges: VisualBinRange[] = [];
+    @observable public ValueRange: number[] = [];
+    @observable public SizeConverter: SizeConverter = new SizeConverter();
+
+    @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); }
+    @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; }
+    @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;
+    }
+
+    constructor(props: FieldViewProps) {
+        super(props);
+    }
+
+    @action
+    dropX = (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField);
+            if (h && h != FieldWaiting) {
+                this.HistoOp.X = h.Data.X;
+            }
+            e.stopPropagation();
+            e.preventDefault();
+        }
+    }
+    @action
+    dropY = (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField);
+            if (h && h != FieldWaiting) {
+                this.HistoOp.Y = h.Data.X;
+            }
+            e.stopPropagation();
+            e.preventDefault();
+        }
+    }
+
+    componentDidMount() {
+        if (this._dropXRef.current) {
+            this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } });
+        }
+        if (this._dropYRef.current) {
+            this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: 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.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 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]);
+                }
+            });
+    }
+
+    @action
+    xLabelPointerDown = (e: React.PointerEvent) => {
+
+    }
+
+    componentWillUnmount() {
+        if (this._dropXDisposer)
+            this._dropXDisposer();
+        if (this._dropYDisposer)
+            this._dropYDisposer();
+    }
+
+    activateHistogramOperation(catalog?: Catalog) {
+        if (catalog) {
+            this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
+                this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
+                if (this.HistoOp != HistogramOperation.Empty) {
+                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), docs => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
+                }
+            }));
+        }
+    }
+    render() {
+        let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
+        let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
+        var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
+        var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
+        let loff = this.SizeConverter.LeftOffset;
+        let toff = this.SizeConverter.TopOffset;
+        let roff = this.SizeConverter.RightOffset;
+        let boff = this.SizeConverter.BottomOffset;
+        return (
+            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}>
+                {({ measureRef }) =>
+                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-w / 2}px, ${-h / 2}px)` }}>
+                        <div className="histogrambox-yaxislabel" ref={this._dropYRef} >
+                            <span className="histogrambox-yaxislabel-text">
+                                {labelY}
+                            </span>
+                        </div>
+                        <div 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>
+                }
+            </Measure>
+        )
+    }
+}
+
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
new file mode 100644
index 000000000..9d42219cc
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
@@ -0,0 +1,26 @@
+.histogramboxprimitives-border {
+    border: 3px;
+    border-style: solid;
+    border-color: white;
+    pointer-events: none;
+    position: absolute;
+}
+.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-line {
+    position: absolute;
+    background: darkGray;
+    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
new file mode 100644
index 000000000..d2f1be4fd
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -0,0 +1,341 @@
+import React = require("react")
+import { computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Utils as DashUtils } from '../../../Utils';
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { FilterModel } from "../../northstar/core/filter/FilterModel";
+import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
+import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+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 { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
+import "./HistogramBoxPrimitives.scss";
+
+
+@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 binPrimitives() {
+        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, key) => {
+            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[]);
+    }
+
+    private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
+        let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
+        return !allBrushPrim ? () => { } : () => 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 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[]);
+    }
+
+    drawEntity(xFrom: number, yFrom: number, entity: JSX.Element) {
+        let transXpercent = xFrom / this.renderDimension * 100;
+        let transYpercent = yFrom / this.renderDimension * 100;
+        return (<div key={DashUtils.GenerateGuid()} className={`histogramboxprimitives-placer`} style={{ transform: `translate(${transXpercent}%, ${transYpercent}%)` }}>
+            {entity}
+        </div>);
+    }
+    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 = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`;
+        let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`;
+        let line = (<div className="histogramboxprimitives-line" style={{ width: trans2Xpercent, height: trans2Ypercent, }} />);
+        return this.drawEntity(xFrom, yFrom, line);
+    }
+
+    drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) {
+        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 widthPercent = r.width / this.renderDimension * 100;
+        let heightPercent = r.height / this.renderDimension * 100;
+        let rect = (<div className={`histogramboxprimitives-${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
+            style={{
+                borderBottomStyle: barAxis == 1 ? "none" : "solid",
+                borderLeftStyle: barAxis == 0 ? "none" : "solid",
+                width: `${widthPercent}%`,
+                height: `${heightPercent}%`,
+                background: color ? `${LABColor.RGBtoHexString(color)}` : ""
+            }}
+        />);
+        return this.drawEntity(r.x, r.y, rect);
+    }
+    render() {
+        return <div className="histogramboxprimitives-container" style={{
+            width: "100%",
+            height: "100%",
+        }}>
+            {this.xaxislines}
+            {this.yaxislines}
+            {this.binPrimitives}
+            {this.selectedPrimitives}
+        </div>
+    }
+}
+
+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.X.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.histoOp.getValue(normalization, bin, this.histoResult, Brush.brushIndex!);
+                return aggResult != undefined && aggResult > prev ? aggResult : prev;
+            }, Number.MIN_VALUE)
+        };
+    }
+    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
+
+        let unNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, 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.histoOp.getValue(2, bin, this.histoResult, 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._histoBox.HistoOp.getValue(2, bin, this.histoResult, 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.histoOp.getValue(1, bin, this.histoResult, 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.histoOp.getValue(0, bin, this.histoResult, 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;
+    }
+
+
+    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);
+        var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult;
+        return !marginResult ? 0 : marginResult.absolutMargin!;
+    }
+
+    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 {
+        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 (this.histoOp.BrushColors.length > 0) {
+            return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        }
+        return StyleConstants.HIGHLIGHT_COLOR;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
new file mode 100644
index 000000000..304d33771
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
@@ -0,0 +1,13 @@
+
+  .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
new file mode 100644
index 000000000..45b23874d
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -0,0 +1,78 @@
+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, HistogramPrimitivesProps } from "./HistogramBox";
+import "./HistogramLabelPrimitives.scss";
+
+@observer
+export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
+    componentDidMount() {
+        reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
+            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, 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.PanelWidth : this.props.HistoBox.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.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.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/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index 8de0bd260..d0711fb69 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -13,11 +13,11 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
 
 export class ModelHelpers {
 
-    public static CreateAggregateKey(atm: AttributeTransformationModel, histogramResult: HistogramResult,
+    public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult,
         brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
         {
             if (aggParameters == undefined) {
-                aggParameters = ModelHelpers.GetAggregateParameter(atm);
+                aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm);
             }
             else {
                 aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
@@ -34,40 +34,40 @@ export class ModelHelpers {
         return ArrayUtil.IndexOfWithEqual(histogramResult.aggregateParameters!, aggParameters);
     }
 
-    public static GetAggregateParameter(atm: AttributeTransformationModel): AggregateParameters | undefined {
+    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 = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+            avg.distinctAttributeParameters = distinctAttributeParameters;
             aggParam = avg;
         }
         else if (atm.AggregateFunction === AggregateFunction.Count) {
             var cnt = new CountAggregateParameters();
             cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            cnt.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+            cnt.distinctAttributeParameters = distinctAttributeParameters;
             aggParam = cnt;
         }
         else if (atm.AggregateFunction === AggregateFunction.Sum) {
             var sum = new SumAggregateParameters();
             sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            sum.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+            sum.distinctAttributeParameters = distinctAttributeParameters;
             aggParam = sum;
         }
         return aggParam;
     }
 
-    public static GetAggregateParametersWithMargins(atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
+    public static GetAggregateParametersWithMargins(distinctAttributeParameters: AttributeParameters | undefined, atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
         var aggregateParameters = new Array<AggregateParameters>();
         atms.forEach(agg => {
-            var aggParams = ModelHelpers.GetAggregateParameter(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 = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+                margin.distinctAttributeParameters = distinctAttributeParameters;
                 aggregateParameters.push(margin);
             }
         });
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
index 9eae39800..53d585bb4 100644
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -1,4 +1,4 @@
-import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult } from "../idea/idea";
+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";
@@ -30,13 +30,13 @@ export class VisualBinRangeHelper {
         throw new Exception()
     }
 
-    public static GetVisualBinRange(dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
+    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(attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
+            var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
             var minValue = Number.MAX_VALUE;
             var maxValue = Number.MIN_VALUE;
             for (var b = 0; b < histoResult.brushes!.length; b++) {
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 7db6fcb91..94e0849af 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -10,9 +10,9 @@ export abstract class BaseOperation {
 
     @observable public Error: string = "";
     @observable public OverridingFilters: FilterModel[] = [];
-    @observable public Result: Result | undefined;
+    @observable public Result?: Result = undefined;
     @observable public ComputationStarted: boolean = false;
-    public OperationReference: OperationReference | undefined = undefined;
+    public OperationReference?: OperationReference = undefined;
 
     private static _nextId = 0;
     public RequestSalt: string = "";
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 6c7288d42..8a0f648f6 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -27,6 +27,8 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @observable public V: AttributeTransformationModel;
     @observable public BrusherModels: BrushLinkModel<HistogramOperation>[] = [];
     @observable public BrushableModels: BrushLinkModel<HistogramOperation>[] = [];
+    @observable public SchemaName: string;
+    @computed public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
 
     @action
     public AddFilterModels(filterModels: FilterModel[]): void {
@@ -38,23 +40,24 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     }
 
     public getValue(axis: number, bin: Bin, result: HistogramResult, brushIndex: number) {
-        var aggregateKey = ModelHelpers.CreateAggregateKey(axis == 0 ? this.X : axis == 1 ? this.Y : this.V, result, brushIndex);
+        var aggregateKey = ModelHelpers.CreateAggregateKey(this.Schema!.distinctAttributeParameters, axis == 0 ? this.X : axis == 1 ? this.Y : this.V, result, brushIndex);
         let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
         return dataValue != null && dataValue.hasResult ? dataValue.result : undefined;
     }
 
-    public static Empty = new HistogramOperation(new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
+    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())));
 
     Equals(other: Object): boolean {
         throw new Error("Method not implemented.");
     }
 
-    constructor(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
+    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;
     }
 
     @computed
@@ -93,7 +96,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
 
         let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float];
-        let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(allAttributes);
+        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))
@@ -112,7 +115,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
             let [perBinAggregateParameters, globalAggregateParameters] = this.GetAggregateParameters(this.X, this.Y, this.V);
             return new HistogramOperationParameters({
                 enableBrushComputation: true,
-                adapterName: CurrentUserUtils.ActiveSchema!.displayName,
+                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),
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index ffd162a83..30627dfd5 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -68,10 +68,14 @@ export class SizeConverter {
         return [undefined, undefined];
     }
 
-    public DataToScreenAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
+    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;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 09778ac77..2f20b102c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -58,7 +58,7 @@ export class Main extends React.Component {
     @observable private mainfreeform?: Document;
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
-    private _northstarColumns: Document[] = [];
+    private _northstarSchemas: Document[] = [];
 
     @computed private get mainContainer(): Document | undefined {
         let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document);
@@ -245,7 +245,7 @@ export class Main extends React.Component {
         let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
         let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
-        let addTreeNode = action(() => Documents.TreeDocument(this._northstarColumns, { width: 200, height: 200, title: "a tree collection" }));
+        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 100, height: 400, title: "northstar schemas" }));
         let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
         let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
         let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
@@ -334,24 +334,27 @@ export class Main extends React.Component {
     // --------------- Northstar hooks ------------- /
 
     @action SetNorthstarCatalog(ctlog: Catalog) {
+        CurrentUserUtils.NorthstarDBCatalog = ctlog;
         if (ctlog && ctlog.schemas) {
-            CurrentUserUtils.ActiveSchema = ArrayUtil.FirstOrDefault<Schema>(ctlog.schemas!, (s: Schema) => s.displayName === "mimic");
-            CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => {
-                Server.GetField(attr.displayName!, action((field: Opt<Field>) => {
-                    if (field instanceof Document) {
-                        this._northstarColumns.push(field);
-                    } else {
-                        var atmod = new ColumnAttributeModel(attr);
-                        let histoOp = new HistogramOperation(
-                            new AttributeTransformationModel(atmod, AggregateFunction.None),
-                            new AttributeTransformationModel(atmod, AggregateFunction.Count),
-                            new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                        this._northstarColumns.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName!, northstarSchema: CurrentUserUtils.ActiveSchema!.displayName! }, attr.displayName!));
-                    }
-                }));
+            this._northstarSchemas = ctlog.schemas.map(schema => {
+                let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! });
+                let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]);
+                CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+                    Server.GetField(attr.displayName!, action((field: Opt<Field>) => {
+                        if (field instanceof Document) {
+                            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(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, attr.displayName!));
+                        }
+                    }));
+                });
+                return schemaDoc;
             })
-            console.log("Activating schema " + CurrentUserUtils.ActiveSchema!.displayName!)
-            CurrentUserUtils.ActiveSchemaName = CurrentUserUtils.ActiveSchema!.displayName!;
         }
     }
     async initializeNorthstar(): Promise<void> {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index b54744337..77551649c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,8 +19,7 @@ import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
-import { HistogramBox } from "./HistogramBox";
-import { HistogramBoxPrimitives } from "./HistogramBoxPrimitives";
+import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -54,7 +53,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             return <p>Error loading layout keys</p>;
         }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, HistogramBoxPrimitives }}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 769d893f3..71613ca4f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -120,9 +120,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     private dropDisposer?: DragManager.DragDropDisposer;
-    protected createDropTarget = (ele: HTMLDivElement) => {
-
-    }
 
     componentDidMount() {
         if (this._mainCont.current) {
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
deleted file mode 100644
index 2660b1b75..000000000
--- a/src/client/views/nodes/HistogramBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.histogrambox-container {
-    padding: 0vw;
-    position: absolute;
-    text-align: center;
-    width: 100%;
-    height: 100%;
-  }
-  .histogrambox-xaxislabel {
-    position:absolute;
-    width:100%;
-    text-align: center;
-    bottom:0;
-    font-size: 14;
-    font-weight: bold;
-  }
-  
-  .histogrambox-container {
-    position:absolute;
-    width:100%;
-    height: 100%;
-  }
-  
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
deleted file mode 100644
index 3307925a2..000000000
--- a/src/client/views/nodes/HistogramBox.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React = require("react")
-import { computed, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import Measure from "react-measure";
-import { Dictionary } from "typescript-collections";
-import { Opt } from "../../../fields/Field";
-import { HistogramField } from "../../../fields/HistogramField";
-import { KeyStore } from "../../../fields/KeyStore";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { FilterModel } from '../../northstar/core/filter/FilterModel';
-import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
-import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
-import { ModelHelpers } from "../../northstar/model/ModelHelpers";
-import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { PIXIRectangle } from "../../northstar/utils/MathUtil";
-import { SizeConverter } from "../../northstar/utils/SizeConverter";
-import "./../../northstar/utils/Extensions";
-import { FieldView, FieldViewProps } from './FieldView';
-import "./HistogramBox.scss";
-import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
-import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
-
-export interface HistogramPrimitivesProps {
-    HistoBox: HistogramBox;
-}
-
-@observer
-export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
-    public HitTargets: Dictionary<PIXIRectangle, FilterModel> = new Dictionary<PIXIRectangle, FilterModel>();
-
-    @observable public PanelWidth: number = 100;
-    @observable public PanelHeight: number = 100;
-    @observable public HistoOp?: HistogramOperation;
-    @observable public VisualBinRanges: VisualBinRange[] = [];
-    @observable public ValueRange: number[] = [];
-    @observable public SizeConverter: SizeConverter = new SizeConverter();
-
-    @computed get createOperationParamsCache() { return this.HistoOp!.CreateOperationParameters(); }
-    @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; }
-    @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;
-    }
-
-    componentDidMount() {
-        reaction(() => [CurrentUserUtils.ActiveSchemaName, this.props.doc.GetText(KeyStore.NorthstarSchema, "?")],
-            (params: string[]) => params[0] === params[1] && this.activateHistogramOperation(), { fireImmediately: true });
-        reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
-        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 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(br, this.HistogramResult!, ind ? this.HistoOp!.Y : this.HistoOp!.X, this.ChartType)));
-
-                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(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.MIN_VALUE, Number.MAX_VALUE]);
-                }
-            });
-    }
-
-    activateHistogramOperation() {
-        this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => {
-            if (histoOp) {
-                runInAction(() => this.HistoOp = histoOp.Data);
-                reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), docs => this.HistoOp!.Links.splice(0, this.HistoOp!.Links.length, ...docs), { fireImmediately: true });
-                reaction(() => this.createOperationParamsCache, () => this.HistoOp!.Update(), { fireImmediately: true });
-            }
-        })
-    }
-    render() {
-        let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>";
-        var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
-        var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
-        let loff = this.SizeConverter.LeftOffset;
-        let toff = this.SizeConverter.TopOffset;
-        let roff = this.SizeConverter.RightOffset;
-        let boff = this.SizeConverter.BottomOffset;
-        return (
-            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}>
-                {({ measureRef }) =>
-                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-w / 2}px, ${-h / 2}px)` }}>
-                        <div style={{
-                            transform: `translate(${loff}px, ${toff}px)`,
-                            width: `calc(100% - ${loff + roff}px)`,
-                            height: `calc(100% - ${toff + boff}px)`,
-                        }}>
-                            <HistogramLabelPrimitives HistoBox={this} />
-                            <HistogramBoxPrimitives HistoBox={this} />
-                        </div>
-                        <div className="histogrambox-xaxislabel">{label}</div>
-                    </div>
-                }
-            </Measure>
-        )
-    }
-}
-
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.scss b/src/client/views/nodes/HistogramBoxPrimitives.scss
deleted file mode 100644
index 85f2c092d..000000000
--- a/src/client/views/nodes/HistogramBoxPrimitives.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-.histogramboxprimitives-border {
-    border: 3px;
-    border-style: solid;
-    border-color: #282828;
-    pointer-events: none;
-    position: absolute;
-}
-.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-line {
-    position: absolute;
-    background: lightgray;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
deleted file mode 100644
index f15cb5689..000000000
--- a/src/client/views/nodes/HistogramBoxPrimitives.tsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import React = require("react")
-import { computed, observable, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import { Utils as DashUtils } from '../../../Utils';
-import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
-import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
-import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult, BinLabel } from "../../northstar/model/idea/idea";
-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 { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
-import "./HistogramBoxPrimitives.scss";
-import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { FilterModel } from "../../northstar/core/filter/FilterModel";
-
-
-@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 binPrimitives() {
-        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, key) => {
-            let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox);
-            let filterModel = ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp!.X, this.histoOp!.Y);
-
-            this.props.HistoBox.HitTargets.setValue(drawPrims.HitGeom, filterModel);
-
-            let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex, filterModel);
-            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[]);
-    }
-
-    private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
-        let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
-        return !allBrushPrim ? () => { } : () => 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 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[]);
-    }
-
-    drawEntity(xFrom: number, yFrom: number, entity: JSX.Element) {
-        let transXpercent = xFrom / this.renderDimension * 100;
-        let transYpercent = yFrom / this.renderDimension * 100;
-        return (<div key={DashUtils.GenerateGuid()} className={`histogramboxprimitives-placer`} style={{ transform: `translate(${transXpercent}%, ${transYpercent}%)` }}>
-            {entity}
-        </div>);
-    }
-    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 = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`;
-        let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`;
-        let line = (<div className="histogramboxprimitives-line" style={{ width: trans2Xpercent, height: trans2Ypercent, }} />);
-        return this.drawEntity(xFrom, yFrom, line);
-    }
-
-    drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) {
-        let widthPercent = r.width / this.renderDimension * 100;
-        let heightPercent = r.height / this.renderDimension * 100;
-        let rect = (<div className={`histogramboxprimitives-${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
-            style={{
-                borderBottomStyle: barAxis == 1 ? "none" : "solid",
-                borderLeftStyle: barAxis == 0 ? "none" : "solid",
-                width: `${widthPercent}%`,
-                height: `${heightPercent}%`,
-                background: color ? `${LABColor.RGBtoHexString(color)}` : ""
-            }}
-        />);
-        return this.drawEntity(r.x, r.y, rect);
-    }
-    render() {
-        return <div className="histogramboxprimitives-container" style={{
-            width: "100%",
-            height: "100%",
-        }}>
-            {this.xaxislines}
-            {this.yaxislines}
-            {this.binPrimitives}
-            {this.selectedPrimitives}
-        </div>
-    }
-}
-
-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.X.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.histoOp.getValue(normalization, bin, this.histoResult, Brush.brushIndex!);
-                return aggResult != undefined && aggResult > prev ? aggResult : prev;
-            }, Number.MIN_VALUE)
-        };
-    }
-    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
-
-        let unNormalizedValue = this.histoOp!.getValue(2, bin, this.histoResult, 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.histoOp.getValue(2, bin, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult))
-
-        // bcz: are these calls needed?  
-        let [xFrom, xTo] = this.sizeConverter.DataToScreenAxisRange(this._histoBox.VisualBinRanges, 0, bin);
-        let [yFrom, yTo] = this.sizeConverter.DataToScreenAxisRange(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._histoBox.HistoOp!.getValue(2, bin, this.histoResult, brush.brushIndex!);
-        if (unNormalizedValue != undefined) {
-            let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!));
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(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.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!);
-        if (dataValue != undefined) {
-            let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
-            let [xFrom, xTo] = this.sizeConverter.DataToScreenAxisRange(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.histoOp.getValue(0, bin, this.histoResult, brush.brushIndex!);
-        if (dataValue != undefined) {
-            let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenAxisRange(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;
-    }
-
-
-    private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = axis.AggregateFunction;
-        var marginAggregateKey = ModelHelpers.CreateAggregateKey(axis, this.histoResult, brush.brushIndex!, marginParams);
-        var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult;
-        return !marginResult ? 0 : marginResult.absolutMargin!;
-    }
-
-    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 {
-        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 (this.histoOp.BrushColors.length > 0) {
-            return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
-        }
-        return StyleConstants.HIGHLIGHT_COLOR;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramLabelPrimitives.scss b/src/client/views/nodes/HistogramLabelPrimitives.scss
deleted file mode 100644
index d8ee88d72..000000000
--- a/src/client/views/nodes/HistogramLabelPrimitives.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-
-  .histogramLabelPrimitives-gridlabel {
-    position:absolute;
-    transform-origin: left top;
-    font-size: 11;
-  }
-  .histogramLabelPrimitives-placer {
-    position:absolute;
-    width:100%;
-    height:100%;
-    pointer-events: none;
-  }
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramLabelPrimitives.tsx b/src/client/views/nodes/HistogramLabelPrimitives.tsx
deleted file mode 100644
index 7f365e45b..000000000
--- a/src/client/views/nodes/HistogramLabelPrimitives.tsx
+++ /dev/null
@@ -1,78 +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 "../../northstar/model/binRanges/NominalVisualBinRange";
-import { StyleConstants } from "../../northstar/utils/StyleContants";
-import "./../../northstar/utils/Extensions";
-import "./HistogramLabelPrimitives.scss";
-import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
-
-@observer
-export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
-    componentDidMount() {
-        reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
-            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, 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.PanelWidth : this.props.HistoBox.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.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.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/fields/HistogramField.ts b/src/fields/HistogramField.ts
deleted file mode 100644
index bb0014ab3..000000000
--- a/src/fields/HistogramField.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-import { HistogramOperation } from "../client/northstar/operations/HistogramOperation";
-import { action } from "mobx";
-import { AttributeTransformationModel } from "../client/northstar/core/attribute/AttributeTransformationModel";
-import { ColumnAttributeModel } from "../client/northstar/core/attribute/AttributeModel";
-import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
-
-
-export class HistogramField extends BasicField<HistogramOperation> {
-    constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) {
-        super(data ? data : HistogramOperation.Empty, save, id);
-    }
-
-    toString(): string {
-        return JSON.stringify(this.Data);
-    }
-
-    Copy(): Field {
-        return new HistogramField(this.Data);
-    }
-
-    ToScriptString(): string {
-        return `new HistogramField("${this.Data}")`;
-    }
-
-    ToJson(): { type: Types, data: string, _id: string } {
-        return {
-            type: Types.HistogramOp,
-            data: JSON.stringify(this.Data),
-            _id: this.Id
-        }
-    }
-
-    @action
-    static FromJson(id: string, data: any): HistogramField {
-        let jp = JSON.parse(data);
-        let X: AttributeTransformationModel | undefined;
-        let Y: AttributeTransformationModel | undefined;
-        let V: AttributeTransformationModel | undefined;
-
-        CurrentUserUtils.GetAllNorthstarColumnAttributes().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 HistogramField(new HistogramOperation(X, Y, V, jp.Normalization), id, false);
-        }
-        return new HistogramField(HistogramOperation.Empty, id, false);
-    }
-}
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 09dddf962..aa0b9ce92 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -44,5 +44,4 @@ export namespace KeyStore {
     export const Archives = new Key("Archives");
     export const Updated = new Key("Updated");
     export const Workspaces = new Key("Workspaces");
-    export const NorthstarSchema = new Key("NorthstarSchema");
 }
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index f958df04b..98a7a1451 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -17,8 +17,7 @@ import { VideoField } from '../fields/VideoField';
 import { InkField } from '../fields/InkField';
 import { PDFField } from '../fields/PDFField';
 import { TupleField } from '../fields/TupleField';
-import { HistogramField } from '../fields/HistogramField';
-
+import { HistogramField } from '../client/northstar/dash-fields/HistogramField';
 
 
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 4b42e40b6..0ac85b446 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -7,8 +7,9 @@ import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { Documents } from "../../../client/documents/Documents";
-import { Schema, Attribute, AttributeGroup } from "../../../client/northstar/model/idea/idea";
+import { Schema, Attribute, AttributeGroup, Catalog } from "../../../client/northstar/model/idea/idea";
 import { observable, computed, action } from "mobx";
+import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
 
 export class CurrentUserUtils {
     private static curr_email: string;
@@ -16,8 +17,7 @@ export class CurrentUserUtils {
     private static user_document: Document;
     //TODO tfs: these should be temporary...
     private static mainDocId: string | undefined;
-    private static activeSchema: Schema | undefined;
-    @observable public static ActiveSchemaName: string = "";
+    @observable private static catalog?: Catalog;
 
     public static get email(): string {
         return this.curr_email;
@@ -39,12 +39,18 @@ export class CurrentUserUtils {
         this.mainDocId = id;
     }
 
-
-    public static get ActiveSchema(): Schema | undefined {
-        return this.activeSchema;
+    @computed public static get NorthstarDBCatalog(): Catalog | undefined {
+        return this.catalog;
+    }
+    public static set NorthstarDBCatalog(ctlog: Catalog | undefined) {
+        this.catalog = ctlog;
     }
-    public static GetAllNorthstarColumnAttributes() {
-        if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) {
+    public static GetNorthstarSchema(name: string): Schema | undefined {
+        return !this.catalog || !this.catalog.schemas ? undefined :
+            ArrayUtil.FirstOrDefault<Schema>(this.catalog.schemas, (s: Schema) => s.displayName === name);
+    }
+    public static GetAllNorthstarColumnAttributes(schema: Schema) {
+        if (!schema || !schema.rootAttributeGroup) {
             return [];
         }
         const recurs = (attrs: Attribute[], g: AttributeGroup) => {
@@ -56,13 +62,10 @@ export class CurrentUserUtils {
             }
         };
         const allAttributes: Attribute[] = new Array<Attribute>();
-        recurs(allAttributes, this.ActiveSchema.rootAttributeGroup);
+        recurs(allAttributes, schema.rootAttributeGroup);
         return allAttributes;
     }
 
-    public static set ActiveSchema(id: Schema | undefined) {
-        this.activeSchema = id;
-    }
     private static createUserDocument(id: string): Document {
         let doc = new Document(id);
 
-- 
cgit v1.2.3-70-g09d2


From 1cdb04707301bc51b3ed6dd1c15c79a7b989b7f7 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 1 Apr 2019 19:54:05 -0400
Subject: Started refactoring props and collections

---
 src/client/views/Main.tsx                          |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionPDFView.tsx | 17 ++++++------
 .../views/collections/CollectionSchemaView.tsx     | 13 +++++-----
 .../views/collections/CollectionVideoView.tsx      |  7 ++---
 src/client/views/collections/CollectionView.tsx    | 15 ++++++-----
 .../views/collections/CollectionViewBase.tsx       |  9 ++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 src/client/views/nodes/DocumentContentsView.tsx    | 23 ++++++++++++++---
 src/client/views/nodes/DocumentView.tsx            | 11 +-------
 src/client/views/nodes/FieldView.tsx               | 11 ++++----
 src/client/views/nodes/FormattedTextBox.tsx        | 10 ++++----
 src/client/views/nodes/ImageBox.tsx                |  8 +++---
 src/client/views/nodes/KeyValueBox.tsx             |  4 +--
 src/client/views/nodes/KeyValuePair.tsx            | 13 +++++-----
 src/client/views/nodes/PDFBox.tsx                  | 30 +++++++++++-----------
 src/client/views/nodes/VideoBox.tsx                | 12 ++++-----
 src/client/views/nodes/WebBox.tsx                  |  4 +--
 19 files changed, 98 insertions(+), 97 deletions(-)

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

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 75855c3d1..82fd1e7c9 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -228,7 +228,7 @@ export class Main extends React.Component {
                 PanelWidth={this.pwidthFunc}
                 PanelHeight={this.pheightFunc}
                 isTopMost={true}
-                SelectOnLoad={false}
+                selectOnLoad={false}
                 focus={this.focusDocument}
                 ContainingCollectionView={undefined} />
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f123149dd..c67354139 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -317,7 +317,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     PanelHeight={this._nativeHeight}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
                     isTopMost={true}
-                    SelectOnLoad={false}
+                    selectOnLoad={false}
                     focus={(doc: Document) => { }}
                     ContainingCollectionView={undefined} />
             </div>
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 4d2daf149..6bb3bacc7 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -8,6 +8,7 @@ import { CollectionViewProps } from "./CollectionViewBase";
 import "./CollectionPDFView.scss"
 import React = require("react");
 import { FieldId } from "../../../fields/Field";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 
 
 @observer
@@ -30,7 +31,8 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
             <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
                 <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
                 <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
-            </div>);
+            </div>
+        );
     }
 
     // "inherited" CollectionView API starts here...
@@ -47,13 +49,12 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
         }
     }
 
-    get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; }
-    get subView(): any { return CollectionView.SubView(this); }
-
     render() {
-        return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
-            {this.subView}
-            {this.props.isSelected() ? this.uIButtons : (null)}
-        </div>)
+        return (
+            <div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
+                <CollectionFreeFormView {...CollectionView.SubViewProps(this)} />
+                {this.props.isSelected() ? this.uIButtons : (null)}
+            </div>
+        )
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 0ff6c3b40..0c7e28691 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -73,19 +73,18 @@ export class CollectionSchemaView extends CollectionViewBase {
 
     renderCell = (rowProps: CellInfo) => {
         let props: FieldViewProps = {
-            doc: rowProps.value[0],
+            Document: rowProps.value[0],
             fieldKey: rowProps.value[1],
             isSelected: () => false,
             select: () => { },
             isTopMost: false,
-            bindings: {},
             selectOnLoad: false,
         }
         let contents = (
             <FieldView {...props} />
         )
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc));
+        let onItemDown = setupDrag(reference, () => props.Document, (containingCollection: CollectionView) => this.props.removeDocument(props.Document));
         let applyToDoc = (doc: Document, value: string) => {
             let script = CompileScript(value, { this: doc }, true);
             if (!script.compiled) {
@@ -105,20 +104,20 @@ export class CollectionSchemaView extends CollectionViewBase {
             return false;
         }
         return (
-            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.doc.Id} ref={reference}>
+            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
                 <EditableView
                     display={"inline"}
                     contents={contents}
                     height={56}
                     GetValue={() => {
-                        let field = props.doc.Get(props.fieldKey);
+                        let field = props.Document.Get(props.fieldKey);
                         if (field && field instanceof Field) {
                             return field.ToScriptString();
                         }
                         return field || "";
                     }}
                     SetValue={(value: string) => {
-                        return applyToDoc(props.doc, value);
+                        return applyToDoc(props.Document, value);
                     }}
                     OnFillDown={(value: string) => {
                         this.props.Document.GetTAsync<ListField<Document>>(this.props.fieldKey, ListField).then((val) => {
@@ -298,7 +297,7 @@ export class CollectionSchemaView extends CollectionViewBase {
                         {doc instanceof Document ? <DocumentView Document={doc}
                             AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
                             isTopMost={false}
-                            SelectOnLoad={false}
+                            selectOnLoad={false}
                             ScreenToLocalTransform={this.getPreviewTransform}
                             ContentScaling={this.getContentScaling}
                             PanelWidth={this.getPanelWidth}
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 470a853e3..1f0143d87 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -8,6 +8,7 @@ import { CollectionViewProps } from "./CollectionViewBase";
 import React = require("react");
 import { FieldId } from "../../../fields/Field";
 import "./CollectionVideoView.scss"
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
 
 
 @observer
@@ -116,14 +117,10 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
         }
     }
 
-    get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; }
-    get subView(): any { return CollectionView.SubView(this); }
-
-
     render() {
         trace();
         return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}>
-            {this.subView}
+            <CollectionFreeFormView {...CollectionView.SubViewProps(this)} />
             {this.props.isSelected() ? this.uIButtons : (null)}
         </div>)
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a1498e0ae..e6351618c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -10,7 +10,7 @@ import { NumberField } from "../../../fields/NumberField";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionDockingView } from "./CollectionDockingView";
 import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionViewProps } from "./CollectionViewBase";
+import { CollectionViewProps, SubCollectionViewProps } from "./CollectionViewBase";
 import { CollectionTreeView } from "./CollectionTreeView";
 import { Field, FieldId, FieldWaiting } from "../../../fields/Field";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -39,7 +39,6 @@ export class CollectionView extends React.Component<CollectionViewProps> {
     public active: () => boolean = () => CollectionView.Active(this);
     addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
     removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-    get subView() { return CollectionView.SubView(this); }
 
     public static Active(self: CollectionView): boolean {
         var isSelected = self.props.isSelected();
@@ -140,9 +139,13 @@ export class CollectionView extends React.Component<CollectionViewProps> {
         }
     }
 
-    public static SubView(self: CollectionView) {
-        let subProps = { ...self.props, addDocument: self.addDocument, removeDocument: self.removeDocument, active: self.active, CollectionView: self }
-        switch (self.collectionViewType) {
+    public static SubViewProps(self: CollectionView): SubCollectionViewProps {
+        return { ...self.props, addDocument: self.addDocument, removeDocument: self.removeDocument, active: self.active, CollectionView: self }
+    }
+
+    private get SubView() {
+        let subProps = CollectionView.SubViewProps(this);
+        switch (this.collectionViewType) {
             case CollectionViewType.Freeform: return (<CollectionFreeFormView {...subProps} />)
             case CollectionViewType.Schema: return (<CollectionSchemaView {...subProps} />)
             case CollectionViewType.Docking: return (<CollectionDockingView {...subProps} />)
@@ -153,7 +156,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
 
     render() {
         return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}>
-            {this.subView}
+            {this.SubView}
         </div>)
     }
 }
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 987f3cb6c..c5a6c53b3 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -17,15 +17,10 @@ import { NumberField } from "../../../fields/NumberField";
 import request = require("request");
 import { ServerUtils } from "../../../server/ServerUtil";
 import { Server } from "../../Server";
+import { FieldViewProps } from "../nodes/FieldView";
 
-export interface CollectionViewProps {
-    fieldKey: Key;
-    Document: Document;
+export interface CollectionViewProps extends FieldViewProps {
     ScreenToLocalTransform: () => Transform;
-    isSelected: () => boolean;
-    isTopMost: boolean;
-    select: (ctrlPressed: boolean) => void;
-    bindings: any;
     panelWidth: () => number;
     panelHeight: () => number;
     focus: (doc: Document) => void;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 60fb95ff5..51ab85b71 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -231,7 +231,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
             RemoveDocument: this.props.removeDocument,
             ScreenToLocalTransform: this.getTransform,
             isTopMost: false,
-            SelectOnLoad: document.Id == this._selectOnLoaded,
+            selectOnLoad: document.Id == this._selectOnLoaded,
             PanelWidth: document.Width,
             PanelHeight: document.Height,
             ContentScaling: this.noScaling,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6daf15f5f..0ce991485 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -28,7 +28,7 @@ export class AudioBox extends React.Component<FieldViewProps> {
 
 
     render() {
-        let field = this.props.doc.Get(this.props.fieldKey)
+        let field = this.props.Document.Get(this.props.fieldKey)
         let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
             field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 77551649c..a5e4b1a17 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 { FieldWaiting } from "../../../fields/Field";
+import { FieldWaiting, Field } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
@@ -11,7 +11,7 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { AudioBox } from "./AudioBox";
-import { DocumentViewProps, JsxBindings } from "./DocumentView";
+import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
@@ -21,8 +21,16 @@ import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
+import { Document } from "../../../fields/Document";
+import { FieldViewProps } from "./FieldView";
+import { Without } from "../../../Utils";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
+type BindingProps = Without<FieldViewProps, 'fieldKey'>
+export interface JsxBindings {
+    props: BindingProps;
+    [keyName: string]: BindingProps | Field;
+}
 
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
@@ -36,7 +44,16 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
 
     CreateBindings(): JsxBindings {
-        let bindings: JsxBindings = { ...this.props, };
+        let { Document, isSelected, select, isTopMost, selectOnLoad } = this.props;
+        let bindings: JsxBindings = {
+            props: {
+                Document,
+                isSelected,
+                select,
+                isTopMost,
+                selectOnLoad,
+            }
+        };
         for (const key of this.layoutKeys) {
             bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 71613ca4f..6530dafc6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -32,7 +32,7 @@ export interface DocumentViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Document) => void;
-    SelectOnLoad: boolean;
+    selectOnLoad: boolean;
 }
 export interface JsxArgs extends DocumentViewProps {
     Keys: { [name: string]: Key }
@@ -76,15 +76,6 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
     return args;
 }
 
-export interface JsxBindings {
-    Document: Document;
-    isSelected: () => boolean;
-    select: (isCtrlPressed: boolean) => void;
-    isTopMost: boolean;
-    SelectOnLoad: boolean;
-    [prop: string]: any;
-}
-
 
 
 @observer
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4e83ec7b9..43688989b 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -28,23 +28,22 @@ import { KeyStore } from "../../../fields/KeyStore";
 //
 export interface FieldViewProps {
     fieldKey: Key;
-    doc: Document;
+    Document: Document;
     isSelected: () => boolean;
-    select: () => void;
+    select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
     selectOnLoad: boolean;
-    bindings: any;
 }
 
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
-        return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`;
+        return `<${fieldType.name} {...props} fieldKey={${fieldStr}} />`;
     }
 
     @computed
     get field(): FieldValue<Field> {
-        const { doc, fieldKey } = this.props;
+        const { Document: doc, fieldKey } = this.props;
         return doc.Get(fieldKey);
     }
     render() {
@@ -76,7 +75,7 @@ export class FieldView extends React.Component<FieldViewProps> {
                 PanelWidth={() => 100}
                 PanelHeight={() => 100}
                 isTopMost={true}
-                SelectOnLoad={false}
+                selectOnLoad={false}
                 focus={() => { }}
                 isSelected={() => false}
                 select={() => false}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 512ad7d70..99ba9addc 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -55,7 +55,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
-            const { doc, fieldKey } = this.props;
+            const { Document: doc, fieldKey } = this.props;
             doc.SetDataOnPrototype(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
             // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
         }
@@ -74,7 +74,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
             ]
         };
 
-        let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+        let field = this.props.Document.GetT(this.props.fieldKey, RichTextField);
         if (field && field != FieldWaiting && field.Data) {
             state = EditorState.fromJSON(config, JSON.parse(field.Data));
         } else {
@@ -88,7 +88,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         }
 
         this._reactionDisposer = reaction(() => {
-            const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+            const field = this.props.Document.GetT(this.props.fieldKey, RichTextField);
             return field && field != FieldWaiting ? field.Data : undefined;
         }, (field) => {
             if (field && this._editorView) {
@@ -96,7 +96,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
             }
         })
         if (this.props.selectOnLoad) {
-            this.props.select();
+            this.props.select(false);
             this._editorView!.focus();
         }
     }
@@ -116,7 +116,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
 
     @action
     onChange(e: React.ChangeEvent<HTMLInputElement>) {
-        const { fieldKey, doc } = this.props;
+        const { fieldKey, Document: doc } = this.props;
         doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value))
         // doc.SetData(fieldKey, e.target.value, RichTextField);
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 60d1f7214..9b9dfe645 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -39,7 +39,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+        this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w)
     }
 
     componentDidMount() {
@@ -91,7 +91,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     specificContextMenu = (e: React.MouseEvent): void => {
-        let field = this.props.doc.GetT(this.props.fieldKey, ImageField);
+        let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
         if (field && field !== FieldWaiting) {
             let url = field.Data.href;
             ContextMenu.Instance.addItem({
@@ -103,10 +103,10 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        let field = this.props.doc.Get(this.props.fieldKey);
+        let field = this.props.Document.Get(this.props.fieldKey);
         let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
             field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
-        let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
+        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
         return (
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
                 <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 283c1f732..a3478143d 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -34,7 +34,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key == 'Enter') {
             if (this._keyInput && this._valueInput) {
-                let doc = this.props.doc.GetT(KeyStore.Data, Document);
+                let doc = this.props.Document.GetT(KeyStore.Data, Document);
                 if (!doc || doc == FieldWaiting) {
                     return
                 }
@@ -69,7 +69,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     createTable = () => {
-        let doc = this.props.doc.GetT(KeyStore.Data, Document);
+        let doc = this.props.Document.GetT(KeyStore.Data, Document);
         if (!doc || doc == FieldWaiting) {
             return <tr><td>Loading...</td></tr>
         }
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 7ed5ee272..2fc11328f 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -43,12 +43,11 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
         }
         let props: FieldViewProps = {
-            doc: this.props.doc,
+            Document: this.props.doc,
             fieldKey: this.key,
             isSelected: () => false,
             select: () => { },
             isTopMost: false,
-            bindings: {},
             selectOnLoad: false,
         }
         let contents = (
@@ -61,15 +60,15 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                     <div className="container">
                         <div>{this.key.Name}</div>
                         <button className="delete" onClick={() => {
-                            let field = props.doc.Get(props.fieldKey);
+                            let field = props.Document.Get(props.fieldKey);
                             if (field && field instanceof Field) {
-                                props.doc.Set(props.fieldKey, undefined);
+                                props.Document.Set(props.fieldKey, undefined);
                             }
                         }}>X</button>
                     </div>
                 </td>
                 <td><EditableView contents={contents} height={36} GetValue={() => {
-                    let field = props.doc.Get(props.fieldKey);
+                    let field = props.Document.Get(props.fieldKey);
                     if (field && field instanceof Field) {
                         return field.ToScriptString();
                     }
@@ -82,12 +81,12 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                         }
                         let field = script();
                         if (field instanceof Field) {
-                            props.doc.Set(props.fieldKey, field);
+                            props.Document.Set(props.fieldKey, field);
                             return true;
                         } else {
                             let dataField = ToField(field);
                             if (dataField) {
-                                props.doc.Set(props.fieldKey, dataField);
+                                props.Document.Set(props.fieldKey, dataField);
                                 return true;
                             }
                         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 28a1f9757..3b5e3a570 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -87,8 +87,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @observable private _interactive: boolean = false;
     @observable private _loaded: boolean = false;
 
-    @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 1); }
-    @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); }
+    @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 1); }
+    @computed private get thumbnailPage() { return this.props.Document.GetNumber(KeyStore.ThumbnailPage, -1); }
 
     componentDidMount() {
         this._reactionDisposer = reaction(
@@ -379,10 +379,10 @@ export class PDFBox extends React.Component<FieldViewProps> {
         setTimeout(() => {
             var me = this;
             htmlToImage.toPng(this._mainDiv.current!,
-                { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
+                { width: me.props.Document.GetNumber(KeyStore.NativeWidth, 0), height: me.props.Document.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
                 .then(function (dataUrl: string) {
-                    me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
-                    me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1));
+                    me.props.Document.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
+                    me.props.Document.SetNumber(KeyStore.ThumbnailPage, me.props.Document.GetNumber(KeyStore.CurPage, -1));
                 })
                 .catch(function (error: any) {
                     console.error('oops, something went wrong!', error);
@@ -409,7 +409,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
         }
 
         // bcz: the number of pages should really be set when the document is imported.
-        this.props.doc.SetNumber(KeyStore.NumPages, page._transport.numPages);
+        this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
         if (this._perPageInfo.length == 0) { //Makes sure it only runs once
             this._perPageInfo = [...Array(page._transport.numPages)]
         }
@@ -421,13 +421,13 @@ export class PDFBox extends React.Component<FieldViewProps> {
         // bcz: the nativeHeight should really be set when the document is imported.
         //      also, the native dimensions could be different for different pages of the PDF
         //      so this design is flawed.
-        var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
-        if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) {
+        var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+        if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) {
             var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
-            this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
-            this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight);
+            this.props.Document.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
+            this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight);
         }
-        if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
+        if (!this.props.Document.GetT(KeyStore.Thumbnail, ImageField)) {
             this.saveThumbnail();
         }
     }
@@ -436,8 +436,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
     get pdfContent() {
         let page = this.curPage;
         const renderHeight = 2400;
-        let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
-        let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+        let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
+        let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
         return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
             <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`}>
                 <Measure onResize={this.setScaling}>
@@ -454,7 +454,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @computed
     get pdfRenderer() {
         let proxy = this._loaded ? (null) : this.imageProxyRenderer;
-        let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
+        let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
         if ((!this._interactive && proxy) || !pdfUrl || pdfUrl == FieldWaiting) {
             return proxy;
         }
@@ -468,7 +468,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     @computed
     get imageProxyRenderer() {
-        let thumbField = this.props.doc.Get(KeyStore.Thumbnail);
+        let thumbField = this.props.Document.Get(KeyStore.Thumbnail);
         if (thumbField) {
             let path = thumbField == FieldWaiting || this.thumbnailPage != this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
                 thumbField instanceof ImageField ? thumbField.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7c0db83a8..72495a964 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -20,7 +20,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
         super(props);
     }
 
-    @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); }
+    @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
 
 
     _loaded: boolean = false;
@@ -31,12 +31,12 @@ export class VideoBox extends React.Component<FieldViewProps> {
             // bcz: the nativeHeight should really be set when the document is imported.
             //      also, the native dimensions could be different for different pages of the PDF
             //      so this design is flawed.
-            var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
-            var nativeHeight = this.props.doc.GetNumber(KeyStore.NativeHeight, 0);
+            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
             var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
             if (!nativeHeight && newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) {
-                this.props.doc.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
-                this.props.doc.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+                this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
+                this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight);
             }
         } else {
             this._loaded = true;
@@ -56,7 +56,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        let field = this.props.doc.GetT(this.props.fieldKey, VideoField);
+        let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
         if (!field || field === FieldWaiting) {
             return <div>Loading</div>
         }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2ca8d49ce..92e2fabd4 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -16,10 +16,10 @@ export class WebBox extends React.Component<FieldViewProps> {
         super(props);
     }
 
-    @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); }
+    @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); }
 
     render() {
-        let field = this.props.doc.Get(this.props.fieldKey);
+        let field = this.props.Document.Get(this.props.fieldKey);
         let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
             field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
 
-- 
cgit v1.2.3-70-g09d2


From 5889bf159ee0a0f6567683b2bb8c2475feccf9ec Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 3 Apr 2019 01:38:29 -0400
Subject: Almost compiles

---
 src/Utils.ts                                       | 10 ++++++
 src/client/Server.ts                               |  2 +-
 src/client/documents/Documents.ts                  |  3 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 14 ++++----
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/Transform.ts                       |  2 +-
 src/client/util/type_decls.d                       |  2 +-
 src/client/views/Main.tsx                          | 10 +++---
 .../views/collections/CollectionBaseView.tsx       | 28 +++++++++++++---
 .../views/collections/CollectionDockingView.tsx    |  9 ++---
 src/client/views/collections/CollectionPDFView.tsx | 36 ++++++++++----------
 .../views/collections/CollectionSchemaView.tsx     | 12 +++++--
 .../views/collections/CollectionTreeView.tsx       |  3 +-
 .../views/collections/CollectionVideoView.tsx      | 39 +++++++++++-----------
 src/client/views/collections/CollectionView.tsx    |  4 +--
 .../views/collections/CollectionViewBase.tsx       |  7 ++--
 .../CollectionFreeFormRemoteCursors.tsx            | 17 ++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 +++++++++++------
 src/client/views/nodes/DocumentContentsView.tsx    | 18 +++++++++-
 src/client/views/nodes/DocumentView.tsx            | 37 +++++++++-----------
 src/client/views/nodes/FieldView.tsx               | 17 ++++++----
 src/client/views/nodes/KeyValuePair.tsx            |  4 +++
 src/fields/Field.ts                                |  4 +--
 23 files changed, 181 insertions(+), 129 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index a5d9bd0ca..3eb192eb8 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -86,4 +86,14 @@ export class Utils {
     }
 }
 
+export function returnTrue() {
+    return true;
+}
+
+export function returnFalse() {
+    return false;
+}
+
+export function emptyFunction() { }
+
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file
diff --git a/src/client/Server.ts b/src/client/Server.ts
index 37e3c2c0d..c301b04d3 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -42,7 +42,7 @@ export class Server {
                 reaction(() => {
                     return this.ClientFieldsCached.get(fieldid);
                 }, (field, reaction) => {
-                    if (field !== "<Waiting>") {
+                    if (field !== FieldWaiting) {
                         reaction.dispose()
                         cb(field)
                     }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0bf275df8..15f980895 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -17,7 +17,8 @@ import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 import { Server } from "../Server";
 import { CollectionPDFView } from "../views/collections/CollectionPDFView";
 import { CollectionVideoView } from "../views/collections/CollectionVideoView";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { CollectionView } from "../views/collections/CollectionView";
+import { CollectionViewType } from "../views/collections/CollectionBaseView";
 import { AudioBox } from "../views/nodes/AudioBox";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index a968def96..4457c9502 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -117,14 +117,14 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     activateHistogramOperation(catalog?: Catalog) {
         if (catalog) {
-            this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
+            this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
                 if (this.HistoOp != HistogramOperation.Empty) {
-                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
-                    reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
+                    reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
                         () => {
-                            let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                            let proto = this.props.doc.GetPrototype() as Document;
+                            let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]);
+                            let proto = this.props.Document.GetPrototype() as Document;
                             this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
                                 brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
@@ -139,8 +139,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     render() {
         let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
         let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
-        var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
-        var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
+        var h = this.props.isTopMost ? this.PanelHeight : this.props.Document.GetNumber(KeyStore.Height, 0);
+        var w = this.props.isTopMost ? this.PanelWidth : this.props.Document.GetNumber(KeyStore.Width, 0);
         let loff = this.SizeConverter.LeftOffset;
         let toff = this.SizeConverter.TopOffset;
         let roff = this.SizeConverter.RightOffset;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 70b1c9829..53bbae18d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -110,7 +110,7 @@ export namespace DragManager {
         xOffset?: number;
         yOffset?: number;
         aliasOnDrop?: boolean;
-        removeDocument?: (collectionDrop: CollectionView) => void;
+        moveDocument?: (targetCollection: Document, addDocument: (document: Document) => void) => void;
         [id: string]: any;
     }
     export function StartDocumentDrag(ele: HTMLElement, dragData: DocumentDragData, options?: DragOptions) {
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index 3e1039166..889134e3e 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -3,7 +3,7 @@ export class Transform {
     private _translateY: number = 0;
     private _scale: number = 1;
 
-    static get Identity(): Transform {
+    static Identity(): Transform {
         return new Transform(0, 0, 1);
     }
 
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 4f69053b1..47c3481b2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -181,7 +181,7 @@ declare class Key extends Field {
     Copy(): Field;
     ToScriptString(): string;
 }
-declare type FIELD_WAITING = "<Waiting>";
+declare type FIELD_WAITING = null;
 declare type Opt<T> = T | undefined;
 declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
 // @ts-ignore
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 82fd1e7c9..bcbe6b987 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -6,7 +6,7 @@ import { Document } from '../../fields/Document';
 import { KeyStore } from '../../fields/KeyStore';
 import "./Main.scss";
 import { MessageStore } from '../../server/Message';
-import { Utils } from '../../Utils';
+import { Utils, returnTrue } from '../../Utils';
 import * as request from 'request'
 import * as rp from 'request-promise'
 import { Documents } from '../documents/Documents';
@@ -212,7 +212,6 @@ export class Main extends React.Component {
         this.workspacesShown = !this.workspacesShown;
     }
 
-    screenToLocalTransform = () => Transform.Identity
     pwidthFunc = () => this.pwidth;
     pheightFunc = () => this.pheight;
     focusDocument = (doc: Document) => { }
@@ -221,15 +220,16 @@ export class Main extends React.Component {
     get content() {
         return !this.mainContainer ? (null) :
             <DocumentView Document={this.mainContainer}
-                AddDocument={undefined}
-                RemoveDocument={undefined}
-                ScreenToLocalTransform={this.screenToLocalTransform}
+                addDocument={undefined}
+                removeDocument={undefined}
+                ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={this.noScaling}
                 PanelWidth={this.pwidthFunc}
                 PanelHeight={this.pheightFunc}
                 isTopMost={true}
                 selectOnLoad={false}
                 focus={this.focusDocument}
+                parentActive={returnTrue}
                 ContainingCollectionView={undefined} />
     }
 
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c8c840085..c9f640c4a 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -19,16 +19,21 @@ export enum CollectionViewType {
 }
 
 export interface CollectionRenderProps {
-    addDocument: (document: Document, allowDuplicates: boolean) => void;
+    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
     removeDocument: (document: Document) => boolean;
+    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
     active: () => boolean;
 }
 
 export interface CollectionViewProps extends FieldViewProps {
     onContextMenu?: (e: React.MouseEvent) => void;
-    children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | null;
+    children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null;
+    className?: string;
+    contentRef?: React.Ref<HTMLDivElement>;
 }
 
+export const COLLECTION_BORDER_WIDTH = 1;
+
 export class CollectionBaseView extends React.Component<CollectionViewProps> {
     get collectionViewType(): CollectionViewType {
         let Document = this.props.Document;
@@ -44,7 +49,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     active = (): boolean => {
         var isSelected = this.props.isSelected();
-        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == self);
+        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this);
         var topMost = this.props.isTopMost;
         return isSelected || childSelected || topMost;
     }
@@ -68,7 +73,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     }
 
     @action.bound
-    addDocument(doc: Document, allowDuplicates: boolean): boolean {
+    addDocument(doc: Document, allowDuplicates: boolean = false): boolean {
         let props = this.props;
         var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
         doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
@@ -123,14 +128,27 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         return false
     }
 
+    @action.bound
+    moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => void): boolean {
+        if (this.props.Document === targetCollection) {
+            return false;
+        }
+        if (this.removeDocument(doc)) {
+            addDocument(doc);
+            return true;
+        }
+        return false;
+    }
+
     render() {
         const props: CollectionRenderProps = {
             addDocument: this.addDocument,
             removeDocument: this.removeDocument,
+            moveDocument: this.moveDocument,
             active: this.active
         }
         return (
-            <div className="collectionView-cont" onContextMenu={this.props.onContextMenu}>
+            <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {this.props.children(this.collectionViewType, props)}
             </div>
         )
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c67354139..bb34340fb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,12 +8,12 @@ import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import Measure from "react-measure";
 import { FieldId, Opt, Field } from "../../../fields/Field";
-import { Utils } from "../../../Utils";
+import { Utils, returnTrue } from "../../../Utils";
 import { Server } from "../../Server";
 import { undoBatch } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
 import React = require("react");
 import { SubCollectionViewProps } from "./CollectionViewBase";
 import { ServerUtils } from "../../../server/ServerUtil";
@@ -310,14 +310,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         var content =
             <div className="collectionDockingView-content" ref={this._mainCont}>
                 <DocumentView key={this._document.Id} Document={this._document}
-                    AddDocument={undefined}
-                    RemoveDocument={undefined}
+                    addDocument={undefined}
+                    removeDocument={undefined}
                     ContentScaling={this._contentScaling}
                     PanelWidth={this._nativeWidth}
                     PanelHeight={this._nativeHeight}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
                     isTopMost={true}
                     selectOnLoad={false}
+                    parentActive={returnTrue}
                     focus={(doc: Document) => { }}
                     ContainingCollectionView={undefined} />
             </div>
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 6bb3bacc7..14ed70b8c 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,23 +1,20 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
 import { CollectionViewProps } from "./CollectionViewBase";
 import "./CollectionPDFView.scss"
 import React = require("react");
-import { FieldId } from "../../../fields/Field";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView } from "../nodes/FieldView";
+import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
 
 
 @observer
 export class CollectionPDFView extends React.Component<CollectionViewProps> {
 
     public static LayoutString(fieldKey: string = "DataKey") {
-        return `<${CollectionPDFView.name} Document={Document}
-                    ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
-                    isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+        return FieldView.LayoutString(CollectionPDFView, fieldKey);
     }
 
     private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
@@ -35,26 +32,27 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
         );
     }
 
-    // "inherited" CollectionView API starts here...
-    @observable
-    public SelectedDocs: FieldId[] = []
-    public active: () => boolean = () => CollectionView.Active(this);
-
-    addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
-    removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
-    specificContextMenu = (e: React.MouseEvent): void => {
+    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: "PDFOptions", event: () => { } });
         }
     }
 
-    render() {
+    private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+        let props = { ...renderProps, ...this.props };
         return (
-            <div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
-                <CollectionFreeFormView {...CollectionView.SubViewProps(this)} />
+            <>
+                <CollectionFreeFormView {...props} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
-            </div>
+            </>
+        )
+    }
+
+    render() {
+        return (
+            <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
+                {this.subView}
+            </CollectionBaseView>
         )
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 0c7e28691..0eacd38b0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -22,9 +22,11 @@ import { EditableView } from "../EditableView";
 import { DocumentView } from "../nodes/DocumentView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
-import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { CollectionView } from "./CollectionView";
 import { CollectionViewBase } from "./CollectionViewBase";
 import { TextField } from "../../../fields/TextField";
+import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
+import { emptyFunction, returnFalse } from "../../../Utils";
 
 
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -79,6 +81,9 @@ export class CollectionSchemaView extends CollectionViewBase {
             select: () => { },
             isTopMost: false,
             selectOnLoad: false,
+            ScreenToLocalTransform: Transform.Identity,
+            focus: emptyFunction,
+            active: returnFalse
         }
         let contents = (
             <FieldView {...props} />
@@ -295,15 +300,16 @@ export class CollectionSchemaView extends CollectionViewBase {
                 {({ measureRef }) =>
                     <div className="collectionSchemaView-content" ref={measureRef}>
                         {doc instanceof Document ? <DocumentView Document={doc}
-                            AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
+                            addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
                             isTopMost={false}
                             selectOnLoad={false}
                             ScreenToLocalTransform={this.getPreviewTransform}
                             ContentScaling={this.getContentScaling}
                             PanelWidth={this.getPanelWidth}
                             PanelHeight={this.getPanelHeight}
-                            ContainingCollectionView={this.props.CollectionView}
+                            ContainingCollectionView={undefined}
                             focus={this.focusDocument}
+                            parentActive={this.props.active}
                         /> : null}
                         <input value={this.previewScript} onChange={this.onPreviewScriptChange}
                             style={{ position: 'absolute', bottom: '0px' }} />
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70790af18..6f9ba40d2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -10,9 +10,10 @@ import { ListField } from "../../../fields/ListField";
 import { setupDrag } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
-import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { CollectionView } from "./CollectionView";
 import { CollectionViewBase } from "./CollectionViewBase";
 import React = require("react")
+import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 
 
 export interface TreeViewProps {
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 1f0143d87..7cb461b4d 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -3,12 +3,14 @@ import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
+import { CollectionView } from "./CollectionView";
+import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
 import { CollectionViewProps } from "./CollectionViewBase";
 import React = require("react");
 import { FieldId } from "../../../fields/Field";
 import "./CollectionVideoView.scss"
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView } from "../nodes/FieldView";
 
 
 @observer
@@ -20,9 +22,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
     @observable _isPlaying: boolean = false;
 
     public static LayoutString(fieldKey: string = "DataKey") {
-        return `<${CollectionVideoView.name} Document={Document}
-                    ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
-                    isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+        return FieldView.LayoutString(CollectionVideoView, fieldKey);
     }
     private get uIButtons() {
         let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
@@ -102,26 +102,27 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
 
     }
 
-    // "inherited" CollectionView API starts here...
-
-    @observable
-    public SelectedDocs: FieldId[] = []
-    public active: () => boolean = () => CollectionView.Active(this);
-
-    addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
-    removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
-    specificContextMenu = (e: React.MouseEvent): void => {
+    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: "VideoOptions", event: () => { } });
         }
     }
 
+    private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+        let props = { ...renderProps, ...this.props };
+        return (
+            <>
+                <CollectionFreeFormView {...props} />
+                {this.props.isSelected() ? this.uIButtons : (null)}
+            </>
+        )
+    }
+
     render() {
         trace();
-        return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}>
-            <CollectionFreeFormView {...CollectionView.SubViewProps(this)} />
-            {this.props.isSelected() ? this.uIButtons : (null)}
-        </div>)
+        return (
+            <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}>
+                {this.subView}
+            </CollectionBaseView>)
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 51cc99595..d440dcff9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,7 +12,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        let props = { ...renderProps, ...this.props };
+        let props = { ...this.props, ...renderProps };
         switch (type) {
             case CollectionViewType.Freeform: return (<CollectionFreeFormView {...props} />)
             case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />)
@@ -34,7 +34,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
         return (
             <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}>
                 {this.SubView}
-            </CollectionBaseView> >
+            </CollectionBaseView>
         )
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index ca4f3b12f..4a0b2312e 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -20,6 +20,9 @@ import { Server } from "../../Server";
 import { FieldViewProps } from "../nodes/FieldView";
 
 export interface CollectionViewProps extends FieldViewProps {
+    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument: (document: Document) => boolean;
+    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
@@ -72,8 +75,8 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
                     de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null));
             }
             let added = this.props.addDocument(de.data.droppedDocument, false);
-            if (added && de.data.removeDocument && !de.data.aliasOnDrop) {
-                de.data.removeDocument(this.props.CollectionView);
+            if (added && de.data.moveDocument && !de.data.aliasOnDrop) {
+                de.data.moveDocument(this.props.Document, this.props.addDocument);
             }
             e.stopPropagation();
             return added;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 19382e66f..f664f4e65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,21 +1,8 @@
-import { action, computed, observable } from "mobx";
+import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
 import { KeyStore } from "../../../../fields/KeyStore";
-import { TextField } from "../../../../fields/TextField";
-import { DragManager } from "../../../util/DragManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { InkingCanvas } from "../../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
-import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase";
-import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import { CollectionViewProps, CursorEntry } from "../CollectionViewBase";
 import "./CollectionFreeFormView.scss";
-import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 51ab85b71..144d121db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -11,7 +11,7 @@ import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
+import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView";
 import { CollectionViewBase } from "../CollectionViewBase";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import "./CollectionFreeFormView.scss";
@@ -20,6 +20,8 @@ import React = require("react");
 import v5 = require("uuid/v5");
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import { PreviewCursor } from "./PreviewCursor";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { SelectionManager } from "../../../util/SelectionManager";
 
 @observer
 export class CollectionFreeFormView extends CollectionViewBase {
@@ -39,8 +41,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
     }
 
     public selectDocuments = (docs: Document[]) => {
-        this.props.CollectionView.SelectedDocs.length = 0;
-        docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+        SelectionManager.DeselectAll;
+        docs.map(doc => {
+            const dv = DocumentManager.Instance.getDocumentView(doc);
+            if (dv) {
+                SelectionManager.SelectDoc(dv, true);
+            }
+        })
     }
 
     public getActiveDocuments = () => {
@@ -59,6 +66,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @observable private _lastX: number = 0;
     @observable private _lastY: number = 0;
 
+    private outerElement?: HTMLDivElement;
+
     @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
     @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
     @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
@@ -66,8 +75,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
     @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
     @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
-    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
+    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) && this.outerElement ? this.outerElement.clientWidth / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
+    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) && this.outerElement ? this.outerElement.clientHeight / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
 
     @undoBatch
     @action
@@ -227,16 +236,17 @@ export class CollectionFreeFormView extends CollectionViewBase {
     getDocumentViewProps(document: Document): DocumentViewProps {
         return {
             Document: document,
-            AddDocument: this.props.addDocument,
-            RemoveDocument: this.props.removeDocument,
+            addDocument: this.props.addDocument,
+            removeDocument: this.props.removeDocument,
             ScreenToLocalTransform: this.getTransform,
             isTopMost: false,
             selectOnLoad: document.Id == this._selectOnLoaded,
             PanelWidth: document.Width,
             PanelHeight: document.Height,
             ContentScaling: this.noScaling,
-            ContainingCollectionView: this.props.CollectionView,
-            focus: this.focusDocument
+            ContainingCollectionView: undefined,
+            focus: this.focusDocument,
+            parentActive: this.props.active,
         }
     }
 
@@ -266,7 +276,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
     getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
-    getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
+    getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
     childViews = () => this.views;
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a5e4b1a17..2574b374a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -44,7 +44,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
 
     CreateBindings(): JsxBindings {
-        let { Document, isSelected, select, isTopMost, selectOnLoad } = this.props;
+        let
+            {
+                Document,
+                isSelected,
+                select,
+                isTopMost,
+                selectOnLoad,
+                ScreenToLocalTransform,
+                addDocument,
+                removeDocument,
+                parentActive: active
+            } = this.props;
         let bindings: JsxBindings = {
             props: {
                 Document,
@@ -52,6 +63,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 select,
                 isTopMost,
                 selectOnLoad,
+                ScreenToLocalTransform,
+                active,
+                addDocument,
+                removeDocument,
+                focus,
             }
         };
         for (const key of this.layoutKeys) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6530dafc6..545344024 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,19 +13,21 @@ import { DragManager } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
 import { ServerUtils } from "../../../server/ServerUtil";
+import { CollectionViewType } from "../collections/CollectionBaseView";
 
 
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView>;
     Document: Document;
-    AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean;
-    RemoveDocument?: (doc: Document) => boolean;
+    addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (doc: Document) => boolean;
+    moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
     ScreenToLocalTransform: () => Transform;
     isTopMost: boolean;
     ContentScaling: () => number;
@@ -33,6 +35,7 @@ export interface DocumentViewProps {
     PanelHeight: () => number;
     focus: (doc: Document) => void;
     selectOnLoad: boolean;
+    parentActive: () => boolean;
 }
 export interface JsxArgs extends DocumentViewProps {
     Keys: { [name: string]: Key }
@@ -83,9 +86,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
     private _downX: number = 0;
     private _downY: number = 0;
-    private _reactionDisposer: Opt<IReactionDisposer>;
-    @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
-    @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
+    @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+    @computed get topMost(): boolean { return this.props.isTopMost; }
     @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
     @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
     @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
@@ -119,12 +121,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         runInAction(() => {
             DocumentManager.Instance.DocumentViews.push(this);
         })
-        this._reactionDisposer = reaction(
-            () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(),
-            () => {
-                if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.indexOf(this.props.Document.Id) != -1)
-                    SelectionManager.SelectDoc(this, true);
-            });
     }
 
     componentDidUpdate() {
@@ -144,9 +140,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
 
         })
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
     }
 
     startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
@@ -156,9 +149,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             dragData.aliasOnDrop = dropAliasOfDraggedDoc;
             dragData.xOffset = x - left;
             dragData.yOffset = y - top;
-            dragData.removeDocument = (dropCollectionView: CollectionView) => {
-                if (this.props.RemoveDocument && this.props.ContainingCollectionView !== dropCollectionView) {
-                    this.props.RemoveDocument(this.props.Document);
+            dragData.moveDocument = (targetCollection: Document, addDocument: (document: Document) => void) => {
+                if (this.props.moveDocument) {
+                    this.props.moveDocument(this.props.Document, targetCollection, addDocument)
                 }
             }
             DragManager.StartDocumentDrag(this._mainCont.current, dragData, {
@@ -197,14 +190,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     deleteClicked = (): void => {
-        if (this.props.RemoveDocument) {
-            this.props.RemoveDocument(this.props.Document);
+        if (this.props.removeDocument) {
+            this.props.removeDocument(this.props.Document);
         }
     }
 
     fieldsClicked = (e: React.MouseEvent): void => {
-        if (this.props.AddDocument) {
-            this.props.AddDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+        if (this.props.addDocument) {
+            this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 2a7d3175f..57af55f9f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -19,6 +19,7 @@ import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
 import { KeyStore } from "../../../fields/KeyStore";
+import { returnFalse } from "../../../Utils";
 
 
 //
@@ -33,8 +34,9 @@ export interface FieldViewProps {
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
     selectOnLoad: boolean;
-    addDocument: (document: Document, allowDuplicates: boolean) => boolean;
-    removeDocument: (document: Document) => boolean;
+    addDocument?: (document: Document, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (document: Document) => boolean;
+    moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => void) => boolean;
     ScreenToLocalTransform: () => Transform;
     active: () => boolean;
     focus: (doc: Document) => void;
@@ -73,19 +75,20 @@ export class FieldView extends React.Component<FieldViewProps> {
         }
         else if (field instanceof Document) {
             return (<DocumentContentsView Document={field}
-                AddDocument={undefined}
-                RemoveDocument={undefined}
-                ScreenToLocalTransform={() => Transform.Identity}
+                addDocument={undefined}
+                removeDocument={undefined}
+                ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={() => 1}
                 PanelWidth={() => 100}
                 PanelHeight={() => 100}
-                isTopMost={true}
+                isTopMost={true} //TODO Why is this top most?
                 selectOnLoad={false}
                 focus={() => { }}
                 isSelected={() => false}
                 select={() => false}
                 layoutKey={KeyStore.Layout}
-                ContainingCollectionView={undefined} />)
+                ContainingCollectionView={undefined}
+                parentActive={this.props.active} />)
         }
         else if (field instanceof ListField) {
             return (<div>
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 2fc11328f..e178cbc8d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -11,6 +11,7 @@ import { Key } from '../../../fields/Key';
 import { Server } from "../../Server"
 import { EditableView } from "../EditableView";
 import { CompileScript, ToField } from "../../util/Scripting";
+import { Transform } from '../../util/Transform';
 
 // Represents one row in a key value plane
 
@@ -49,6 +50,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             select: () => { },
             isTopMost: false,
             selectOnLoad: false,
+            active: () => false,
+            ScreenToLocalTransform: Transform.Identity,
+            focus: () => { },
         }
         let contents = (
             <FieldView {...props} />
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index d48509a47..0d0e56f77 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -12,8 +12,8 @@ export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T
     return undefined;
 }
 
-export const FieldWaiting: FIELD_WAITING = "<Waiting>";
-export type FIELD_WAITING = "<Waiting>";
+export const FieldWaiting: FIELD_WAITING = null;
+export type FIELD_WAITING = null;
 export type FieldId = string;
 export type Opt<T> = T | undefined;
 export type FieldValue<T> = Opt<T> | FIELD_WAITING;
-- 
cgit v1.2.3-70-g09d2


From 0403ee1f31d3f127a44aaabdfd21785d7efa6430 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 5 Apr 2019 01:17:41 -0400
Subject: Fixed remaining linter warnings

---
 src/client/northstar/core/BaseObject.ts            |   2 +-
 .../northstar/core/attribute/AttributeModel.ts     |   4 +-
 .../core/attribute/AttributeTransformationModel.ts |   2 +-
 .../core/attribute/CalculatedAttributeModel.ts     |   4 +-
 src/client/northstar/core/filter/FilterModel.ts    |   6 +-
 .../northstar/core/filter/ValueComparision.ts      |   8 +-
 src/client/northstar/dash-fields/HistogramField.ts |   8 +-
 .../dash-nodes/HistogramBinPrimitiveCollection.ts  |  46 ++++-----
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  12 +--
 .../dash-nodes/HistogramBoxPrimitives.tsx          |  18 ++--
 .../dash-nodes/HistogramLabelPrimitives.tsx        |  10 +-
 src/client/northstar/manager/Gateway.ts            |   8 +-
 src/client/northstar/model/ModelExtensions.ts      |  14 +--
 src/client/northstar/model/ModelHelpers.ts         |  10 +-
 .../model/binRanges/DateTimeVisualBinRange.ts      |  24 ++---
 .../northstar/model/binRanges/VisualBinRange.ts    |   4 -
 .../model/binRanges/VisualBinRangeHelper.ts        |   7 +-
 .../northstar/model/idea/MetricTypeMapping.ts      |  28 +++---
 src/client/northstar/operations/BaseOperation.ts   |   8 +-
 .../northstar/operations/HistogramOperation.ts     |   2 +-
 src/client/northstar/utils/ArrayUtil.ts            |  14 +--
 src/client/northstar/utils/GeometryUtil.ts         |   2 +-
 src/client/northstar/utils/MathUtil.ts             |  12 +--
 src/client/northstar/utils/SizeConverter.ts        |   4 +-
 src/client/northstar/utils/Utils.ts                |  12 +--
 src/client/views/DocumentDecorations.tsx           |   4 +-
 src/client/views/Main.tsx                          |   4 +-
 .../views/collections/CollectionBaseView.tsx       |   8 +-
 .../views/collections/CollectionDockingView.tsx    |  30 +++---
 src/client/views/collections/CollectionPDFView.tsx |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionVideoView.tsx      |   4 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/CollectionViewBase.tsx       |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  12 +--
 .../collectionFreeForm/PreviewCursor.tsx           |   2 +-
 src/client/views/nodes/Annotation.tsx              | 108 ++++++++++-----------
 src/client/views/nodes/AudioBox.tsx                |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   2 +-
 src/client/views/nodes/KeyValueBox.tsx             |   6 +-
 src/client/views/nodes/LinkBox.tsx                 |   2 +-
 src/client/views/nodes/LinkMenu.tsx                |   2 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 src/fields/AudioField.ts                           |   2 +-
 src/fields/BasicField.ts                           |   2 +-
 src/fields/ImageField.ts                           |   2 +-
 src/fields/ListField.ts                            |  10 +-
 src/fields/PDFField.ts                             |   2 +-
 src/fields/VideoField.ts                           |   2 +-
 src/server/index.ts                                |   2 +-
 52 files changed, 245 insertions(+), 250 deletions(-)

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

diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
index e9e766e31..f1761e643 100644
--- a/src/client/northstar/core/BaseObject.ts
+++ b/src/client/northstar/core/BaseObject.ts
@@ -4,7 +4,7 @@ import { IDisposable } from '../utils/IDisposable'
 export class BaseObject implements IEquatable, IDisposable {
 
     public Equals(other: Object): boolean {
-        return this == other;
+        return this === other;
     }
 
     public Dispose(): void {
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
index 124a5b45a..230bfecc8 100644
--- a/src/client/northstar/core/attribute/AttributeModel.ts
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -34,7 +34,7 @@ export class ColumnAttributeModel extends AttributeModel {
     }
 
     public Equals(other: ColumnAttributeModel): boolean {
-        return this.Attribute.rawName == other.Attribute.rawName;
+        return this.Attribute.rawName === other.Attribute.rawName;
     }
 }
 
@@ -105,7 +105,7 @@ export class BackendAttributeModel extends AttributeModel {
     }
 
     public Equals(other: BackendAttributeModel): boolean {
-        return this.Id == other.Id;
+        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
index cc5aa7154..f50b78d51 100644
--- a/src/client/northstar/core/attribute/AttributeTransformationModel.ts
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -41,7 +41,7 @@ export class AttributeTransformationModel implements IEquatable {
     }
 
     public Equals(other: AttributeTransformationModel): boolean {
-        return this.AggregateFunction == other.AggregateFunction &&
+        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
index ab96c794d..0b8e0d12c 100644
--- a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -11,7 +11,7 @@ export class CalculatedAttributeManager {
     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) {
+                am.Id === id) {
                 return true;
             }
             return false;
@@ -27,7 +27,7 @@ export class CalculatedAttributeManager {
     public static CreateCodeAttributeModel(code: string, codeName: string, visualizationHints: VisualizationHint[]): CodeAttributeModel {
         var filtered = this.AllCalculatedAttributes.filter(am => {
             if (am instanceof CodeAttributeModel &&
-                am.CodeName == codeName) {
+                am.CodeName === codeName) {
                 return true;
             }
             return false;
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index aee99d2b6..20fca77f5 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -46,16 +46,16 @@ export class FilterModel {
         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(" || ") + ")";
+            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 = linkVm.Get(KeyStore.LinkedFromDocs);
-                if (filterDoc && filterDoc != FieldWaiting && filterDoc instanceof Document) {
+                if (filterDoc && filterDoc !== FieldWaiting && filterDoc instanceof Document) {
                     let filterHistogram = filterDoc.GetT(KeyStore.Data, HistogramField);
-                    if (filterHistogram && filterHistogram != FieldWaiting) {
+                    if (filterHistogram && filterHistogram !== FieldWaiting) {
                         if (!visitedFilterProviders.has(filterHistogram.Data)) {
                             let child = FilterModel.GetFilterModelsRecursive(filterHistogram.Data, visitedFilterProviders, filterModels, false);
                             if (child !== "") {
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
index 1e729d06e..1a3e461f5 100644
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -20,7 +20,7 @@ export class ValueComparison {
         if (this.Predicate !== (other as ValueComparison).Predicate)
             return false;
         let isComplex = (typeof this.Value === "object");
-        if (!isComplex && this.Value != (other as ValueComparison).Value)
+        if (!isComplex && this.Value !== (other as ValueComparison).Value)
             return false;
         if (isComplex && !this.Value.Equals((other as ValueComparison).Value))
             return false;
@@ -58,13 +58,13 @@ export class ValueComparison {
         var rawName = this.attributeModel.CodeName;
         switch (this.Predicate) {
             case Predicate.STARTS_WITH:
-                ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
+                ret += rawName + " !== null && " + rawName + ".StartsWith(" + val + ") ";
                 return ret;
             case Predicate.ENDS_WITH:
-                ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
+                ret += rawName + " !== null && " + rawName + ".EndsWith(" + val + ") ";
                 return ret;
             case Predicate.CONTAINS:
-                ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
+                ret += rawName + " !== null && " + rawName + ".Contains(" + val + ") ";
                 return ret;
             default:
                 ret += rawName + " " + op + " " + val + " ";
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 90be70b80..f939ccd83 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -16,7 +16,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
     omitKeys(obj: any, keys: any) {
         var dup: any = {};
         for (var key in obj) {
-            if (keys.indexOf(key) == -1) {
+            if (keys.indexOf(key) === -1) {
                 dup[key] = obj[key];
             }
         }
@@ -54,13 +54,13 @@ export class HistogramField extends BasicField<HistogramOperation> {
         let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
         if (schema) {
             CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) {
+                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) {
+                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) {
+                if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) {
                     V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
                 }
             });
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
index 43e768c62..cdae90c8b 100644
--- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -48,28 +48,28 @@ export class HistogramBinPrimitiveCollection {
 
         // 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);
+        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) {
+            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) {
+                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) {
+            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) {
+                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);
@@ -79,19 +79,19 @@ export class HistogramBinPrimitiveCollection {
             return 0;
         }, 0);
         this.BinPrimitives = this.BinPrimitives.reverse();
-        var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex);
+        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));
+        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;
+                return aggResult !== undefined && aggResult > prev ? aggResult : prev;
             }, Number.MIN_VALUE)
         };
     }
@@ -99,7 +99,7 @@ export class HistogramBinPrimitiveCollection {
     private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
 
         let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
-        if (unNormalizedValue == undefined)
+        if (unNormalizedValue === undefined)
             return brushFactorSum;
 
         var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
@@ -112,7 +112,7 @@ export class HistogramBinPrimitiveCollection {
         let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
 
         var returnBrushFactorSum = brushFactorSum;
-        if (allUnNormalizedValue != undefined) {
+        if (allUnNormalizedValue !== undefined) {
             var brushFactor = (unNormalizedValue / allUnNormalizedValue);
             returnBrushFactorSum += brushFactor;
             returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
@@ -141,11 +141,11 @@ export class HistogramBinPrimitiveCollection {
 
     private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
         let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
-        if (unNormalizedValue != undefined) {
+        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)
+            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;
@@ -153,7 +153,7 @@ export class HistogramBinPrimitiveCollection {
 
     private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
         let dataValue = this.getBinValue(1, bin, brush.brushIndex!);
-        if (dataValue != undefined) {
+        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);
 
@@ -163,14 +163,14 @@ export class HistogramBinPrimitiveCollection {
                 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);
+                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) {
+        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);
 
@@ -181,15 +181,15 @@ export class HistogramBinPrimitiveCollection {
                 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);
+                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);
+        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;
+        return dataValue !== null && dataValue.hasResult ? dataValue.result : undefined;
     }
 
     private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
@@ -218,13 +218,13 @@ export class HistogramBinPrimitiveCollection {
 
     private baseColorFromBrush(brush: Brush): number {
         let bc = StyleConstants.BRUSH_COLORS;
-        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+        if (brush.brushIndex === ModelHelpers.RestBrushIndex(this.histoResult)) {
             return StyleConstants.HIGHLIGHT_COLOR;
         }
-        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+        else if (brush.brushIndex === ModelHelpers.OverlapBrushIndex(this.histoResult)) {
             return StyleConstants.OVERLAP_COLOR;
         }
-        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+        else if (brush.brushIndex === ModelHelpers.AllBrushIndex(this.histoResult)) {
             return 0x00ff00;
         }
         else if (bc.length > 0) {
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 5938bf9b1..c9658d741 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -55,7 +55,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     dropX = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
             let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField);
-            if (h && h != FieldWaiting) {
+            if (h && h !== FieldWaiting) {
                 this.HistoOp.X = h.Data.X;
             }
             e.stopPropagation();
@@ -66,7 +66,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     dropY = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
             let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField);
-            if (h && h != FieldWaiting) {
+            if (h && h !== FieldWaiting) {
                 this.HistoOp.Y = h.Data.X;
             }
             e.stopPropagation();
@@ -76,11 +76,11 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     @action
     xLabelPointerDown = (e: React.PointerEvent) => {
-        this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+        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);
+        this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
     }
 
     componentDidMount() {
@@ -119,7 +119,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         if (catalog) {
             this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
-                if (this.HistoOp != HistogramOperation.Empty) {
+                if (this.HistoOp !== HistogramOperation.Empty) {
                     reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
                     reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
                         () => {
@@ -128,7 +128,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                             this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
                                 brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                                return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] }
+                                return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] }
                             }));
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index e9adb3ce5..66d31846c 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -44,7 +44,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
     }
 
     private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
-        let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
+        let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
         return !allBrushPrim ? () => { } : () => runInAction(() => {
             if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
                 this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim!), 1);
@@ -63,9 +63,9 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels();
         return 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));
+            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[]);
     }
@@ -86,8 +86,8 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
             xFrom += width;
             width = -width;
         }
-        let trans2Xpercent = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`;
-        let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`;
+        let trans2Xpercent = width === 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`;
+        let trans2Ypercent = height === 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`;
         let line = (<div className="histogramboxprimitives-line" style={{ width: trans2Xpercent, height: trans2Ypercent, }} />);
         return this.drawEntity(xFrom, yFrom, line);
     }
@@ -102,10 +102,10 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         }
         let widthPercent = r.width / this.renderDimension * 100;
         let heightPercent = r.height / this.renderDimension * 100;
-        let rect = (<div className={`histogramboxprimitives-${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
+        let rect = (<div className={`histogramboxprimitives-${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler() }}
             style={{
-                borderBottomStyle: barAxis == 1 ? "none" : "solid",
-                borderLeftStyle: barAxis == 0 ? "none" : "solid",
+                borderBottomStyle: barAxis === 1 ? "none" : "solid",
+                borderLeftStyle: barAxis === 0 ? "none" : "solid",
                 width: `${widthPercent}%`,
                 height: `${heightPercent}%`,
                 background: color ? `${LABColor.RGBtoHexString(color)}` : ""
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index 93b237deb..dd62e9146 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -34,7 +34,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
         let vb = this.props.HistoBox.VisualBinRanges;
         if (!vb.length || !sc.Initialized)
             return (null);
-        let dim = (axis == 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis == 0 && vb[axis] instanceof NominalVisualBinRange) ?
+        let dim = (axis === 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
             (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
             sc.MaxLabelSizes[axis].coords[axis] + 5);
 
@@ -47,17 +47,17 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
                 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) {
+                if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) {
                     let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.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.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`
+                let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`
+                let yPercent = axis === 0 ? `${this.props.HistoBox.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)` }}>
+                        <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis === 0 ? sc.LabelAngle : 0}rad)` }}>
                             {label}
                         </div>
                     </div>
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
index 3e72a50ae..ba6fe2ad5 100644
--- a/src/client/northstar/manager/Gateway.ts
+++ b/src/client/northstar/manager/Gateway.ts
@@ -49,7 +49,7 @@ export class Gateway {
 
     public async Compile(data: any): Promise<CompileResults | undefined> {
         const json = await this.MakePostJsonRequest("compile", data);
-        if (json != null) {
+        if (json !== null) {
             const cr = CompileResults.fromJS(json);
             return cr;
         }
@@ -108,7 +108,7 @@ export class Gateway {
 
     public async StartOperation(data: any): Promise<OperationReference | undefined> {
         const json = await this.MakePostJsonRequest("operation", data);
-        if (json != null) {
+        if (json !== null) {
             const or = OperationReference.fromJS(json);
             return or;
         }
@@ -116,7 +116,7 @@ export class Gateway {
 
     public async GetResult(data: any): Promise<Result | undefined> {
         const json = await this.MakePostJsonRequest("result", data);
-        if (json != null) {
+        if (json !== null) {
             const res = Result.fromJS(json);
             return res;
         }
@@ -163,7 +163,7 @@ export class Gateway {
 
     public static ConstructUrl(appendix: string): string {
         let base = Settings.Instance.ServerUrl;
-        if (base.slice(-1) == "/") {
+        if (base.slice(-1) === "/") {
             base = base.slice(0, -1);
         }
         let url = base + "/" + Settings.Instance.ServerApiPath + "/" + appendix;
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
index 9fcba7f1c..e4bf77ed8 100644
--- a/src/client/northstar/model/ModelExtensions.ts
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -3,23 +3,23 @@ import { Utils } from '../utils/Utils'
 
 import { FilterModel } from '../core/filter/FilterModel'
 
-(SingleDimensionAggregateParameters as any).prototype["Equals"] = function (other: Object) {
+(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;
+    if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
     return true;
 }
 
 {
-    (AttributeParameters as any).prototype["Equals"] = function (other: AttributeParameters) {
+    (AttributeParameters as any).prototype.Equals = function (other: AttributeParameters) {
         return (<any>this).constructor.name === (<any>other).constructor.name &&
             this.rawName === other.rawName;
     }
 }
 
 {
-    (Solution as any).prototype["Equals"] = function (other: Object) {
+    (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;
@@ -27,11 +27,11 @@ import { FilterModel } from '../core/filter/FilterModel'
 }
 
 {
-    (MarginAggregateParameters as any).prototype["Equals"] = function (other: Object) {
+    (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 SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
 
         if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
         return true;
@@ -39,7 +39,7 @@ import { FilterModel } from '../core/filter/FilterModel'
 }
 
 {
-    (Brush as any).prototype["Equals"] = function (other: Object) {
+    (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;
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index d0711fb69..1a58e6180 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -16,7 +16,7 @@ export class ModelHelpers {
     public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult,
         brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
         {
-            if (aggParameters == undefined) {
+            if (aggParameters === undefined) {
                 aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm);
             }
             else {
@@ -146,7 +146,7 @@ export class ModelHelpers {
     }
 
     public static GetAggregateResult(bin: Bin, aggregateKey: AggregateKey) {
-        if (aggregateKey.aggregateParameterIndex == -1 || aggregateKey.brushIndex == -1) {
+        if (aggregateKey.aggregateParameterIndex === -1 || aggregateKey.brushIndex === -1) {
             return null;
         }
         return bin.aggregateResults![aggregateKey.aggregateParameterIndex! * bin.ySize! + aggregateKey.brushIndex!];
@@ -157,9 +157,9 @@ export class ModelHelpers {
         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) {
+        if (atm.AttributeModel.DataType === DataType.Float ||
+            atm.AttributeModel.DataType === DataType.Double ||
+            atm.AttributeModel.DataType === DataType.Int) {
             ret.push(AggregateFunction.Avg);
             ret.push(AggregateFunction.Sum);
         }
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
index 9313fb1a7..f5872aa4c 100644
--- a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -39,24 +39,24 @@ export class DateTimeVisualBinRange extends VisualBinRange {
 
     public GetLabel(value: number): string {
         var dt = DateTimeVisualBinRange.TicksToDate(value);
-        if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Second ||
-            this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Minute) {
+        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) {
+        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) {
+        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) {
+        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) {
+        else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
             return "" + dt.getFullYear();
         }
         return "n/a";
@@ -82,22 +82,22 @@ export class DateTimeVisualBinRange extends VisualBinRange {
     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) {
+        if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Second) {
             returnDate = new Date(copiedDate.setSeconds(copiedDate.getSeconds() + dateTimeStep.dateTimeStepValue!));
         }
-        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Minute) {
+        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
             returnDate = new Date(copiedDate.setMinutes(copiedDate.getMinutes() + dateTimeStep.dateTimeStepValue!));
         }
-        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Hour) {
+        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
             returnDate = new Date(copiedDate.setHours(copiedDate.getHours() + dateTimeStep.dateTimeStepValue!));
         }
-        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Day) {
+        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
             returnDate = new Date(copiedDate.setDate(copiedDate.getDate() + dateTimeStep.dateTimeStepValue!));
         }
-        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Month) {
+        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
             returnDate = new Date(copiedDate.setMonth(copiedDate.getMonth() + dateTimeStep.dateTimeStepValue!));
         }
-        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Year) {
+        else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
             returnDate = new Date(copiedDate.setFullYear(copiedDate.getFullYear() + dateTimeStep.dateTimeStepValue!));
         }
         return DateTimeVisualBinRange.DateToTicks(returnDate);
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
index f53008f9a..a0766e494 100644
--- a/src/client/northstar/model/binRanges/VisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -2,10 +2,6 @@ import { BinLabel } from '../../model/idea/idea'
 
 export abstract class VisualBinRange {
 
-    constructor() {
-
-    }
-
     public abstract AddStep(value: number): number;
 
     public abstract GetValueFromIndex(index: number): number;
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
index 53d585bb4..63ee61909 100644
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -39,8 +39,7 @@ export class VisualBinRangeHelper {
             var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
             var minValue = Number.MAX_VALUE;
             var maxValue = Number.MIN_VALUE;
-            for (var b = 0; b < histoResult.brushes!.length; b++) {
-                var brush = histoResult.brushes![b];
+            for (const brush of histoResult.brushes!) {
                 aggregateKey.brushIndex = brush.brushIndex;
                 for (var key in histoResult.bins) {
                     if (histoResult.bins.hasOwnProperty(key)) {
@@ -56,12 +55,12 @@ export class VisualBinRangeHelper {
 
             let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false);
 
-            if (chartType == ChartType.HorizontalBar || chartType == ChartType.VerticalBar) {
+            if (chartType === ChartType.HorizontalBar || chartType === ChartType.VerticalBar) {
                 visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue),
                     Math.max(0, (visualBinRange as QuantitativeVisualBinRange).DataBinRange.maxValue!),
                     SETTINGS_X_BINS, false);
             }
-            else if (chartType == ChartType.SinglePoint) {
+            else if (chartType === ChartType.SinglePoint) {
                 visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), Math.max(0, maxValue),
                     SETTINGS_X_BINS, false);
             }
diff --git a/src/client/northstar/model/idea/MetricTypeMapping.ts b/src/client/northstar/model/idea/MetricTypeMapping.ts
index 11e0c871a..e9759cf16 100644
--- a/src/client/northstar/model/idea/MetricTypeMapping.ts
+++ b/src/client/northstar/model/idea/MetricTypeMapping.ts
@@ -5,20 +5,20 @@ 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) {
+        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;
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index f545b2c58..a14337763 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -29,11 +29,11 @@ export abstract class BaseOperation {
         // let filterModels: FilterModel[] = [];
         // return FilterModel.GetFilterModelsRecursive(this, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, true)
         // if (this.OverridingFilters.length > 0) {
-        //     return "(" + this.OverridingFilters.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+        //     return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
         // }
         // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
         // let sliceModel = this.TypedViewModel.IncomingSliceModel;
-        // if (sliceModel != null && sliceModel.Source != null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
+        // if (sliceModel !== null && sliceModel.Source !== null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
         //     let filterModels = sliceModel.Source.FilterModels.map(f => f);
         //     return FilterModel.GetFilterModelsRecursive(rdg.get(sliceModel.Source), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
         // }
@@ -99,8 +99,8 @@ export abstract class BaseOperation {
                     if (result instanceof ErrorResult) {
                         throw new Error((result as ErrorResult).message);
                     }
-                    if (this.RequestSalt == pollPromise.RequestSalt) {
-                        if (result && (!this.Result || this.Result.progress != result.progress)) {
+                    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.");
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index e63de1632..7f48d1629 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -67,7 +67,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         let brushes: string[] = [];
         this.BrushLinks.map(brushLink => {
             let brushHistogram = brushLink.b.GetT(KeyStore.Data, HistogramField);
-            if (brushHistogram && brushHistogram != FieldWaiting) {
+            if (brushHistogram && brushHistogram !== FieldWaiting) {
                 let filterModels: FilterModel[] = [];
                 brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.Data, new Set<IBaseFilterProvider>(), filterModels, false));
             }
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
index f35c98317..a52ca6d96 100644
--- a/src/client/northstar/utils/ArrayUtil.ts
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -7,14 +7,14 @@ export class ArrayUtil {
             return false;
         }
         let isComplex = typeof arr1[0] === "object";
-        for (let i = 0; i < arr1.length; i++) {
-            if (isComplex && "Equals" in arr1[i]) {
-                if (arr1[i].Equals(arr2)) {
+        for (const ele of arr1) {
+            if (isComplex && "Equals" in ele) {
+                if (ele.Equals(arr2)) {
                     return true;
                 }
             }
             else {
-                if (arr1[i] === arr2) {
+                if (ele === arr2) {
                     return true;
                 }
             }
@@ -63,9 +63,9 @@ export class ArrayUtil {
 
     public static Distinct(arr: any[]): any[] {
         let ret = [];
-        for (let i = 0; i < arr.length; i++) {
-            if (!ArrayUtil.Contains(ret, arr[i])) {
-                ret.push(arr[i]);
+        for (const ele of arr) {
+            if (!ArrayUtil.Contains(ret, ele)) {
+                ret.push(ele);
             }
         }
         return ret;
diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts
index d5f3ba631..6d8acea20 100644
--- a/src/client/northstar/utils/GeometryUtil.ts
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -85,7 +85,7 @@ export class GeometryUtil {
     //         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);
+    //         var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
     //         if (intersect) 
     //            inside = !inside;
     //     }
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
index bb7e73871..7aa255096 100644
--- a/src/client/northstar/utils/MathUtil.ts
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -93,7 +93,7 @@ export class MathUtil {
     public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
         // Return minimum distance between line segment vw and point p
         var 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
+        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
@@ -117,7 +117,7 @@ export class MathUtil {
         var b2 = ps2.x - pe2.x;
 
         var delta = a1 * b2 - a2 * b1;
-        if (delta == 0) {
+        if (delta === 0) {
             return undefined;
         }
         var c2 = a2 * ps2.x + b2 * ps2.y;
@@ -147,19 +147,19 @@ export class MathUtil {
         var ret = new Array<PIXIPoint>();
         var dist = this.Dist(lineFrom, lineTo)
         var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
-        if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
+        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 != null && this.PointInPIXIRectangle(inter, rect) &&
+        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 != null && this.PointInPIXIRectangle(inter, rect) &&
+        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 != null && this.PointInPIXIRectangle(inter, rect) &&
+        if (inter && this.PointInPIXIRectangle(inter, rect) &&
             this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
             ret.push(inter);
         return ret;
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index bb91ed4a7..b5c4a16ab 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -54,7 +54,7 @@ export class SizeConverter {
     }
 
     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 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];
@@ -85,7 +85,7 @@ export class SizeConverter {
         return flip ? (this.RenderDimension) - retY : retY;
     }
     public DataToScreenCoord(v: number, axis: number) {
-        if (axis == 0)
+        if (axis === 0)
             return this.DataToScreenX(v);
         return this.DataToScreenY(v);
     }
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
index b35dce820..c96b4cbd9 100644
--- a/src/client/northstar/utils/Utils.ts
+++ b/src/client/northstar/utils/Utils.ts
@@ -27,17 +27,17 @@ export class Utils {
 
     public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> {
         let typed = <IBaseBrushable<T>>obj;
-        return typed != null && typed.BrusherModels !== undefined;
+        return typed !== null && typed.BrusherModels !== undefined;
     }
 
     public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider {
         let typed = <IBaseFilterProvider>obj;
-        return typed != null && typed.FilterModels !== undefined;
+        return typed !== null && typed.FilterModels !== undefined;
     }
 
     public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer {
         let typed = <IBaseFilterConsumer>obj;
-        return typed != null && typed.FilterOperand !== undefined;
+        return typed !== null && typed.FilterOperand !== undefined;
     }
 
     public static EncodeQueryData(data: any): string {
@@ -63,9 +63,9 @@ export class Utils {
     public static GetQueryVariable(variable: string) {
         let query = window.location.search.substring(1);
         let vars = query.split("&");
-        for (let i = 0; i < vars.length; i++) {
-            let pair = vars[i].split("=");
-            if (decodeURIComponent(pair[0]) == variable) {
+        for (const variable of vars) {
+            let pair = variable.split("=");
+            if (decodeURIComponent(pair[0]) === variable) {
                 return decodeURIComponent(pair[1]);
             }
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 11bf24505..1bb00a3fc 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -60,10 +60,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (text[0] === '#') {
                 let command = text.slice(1, text.length);
                 this._fieldKey = new Key(command)
-                // if (command == "Title" || command == "title") {
+                // if (command === "Title" || command === "title") {
                 //     this._fieldKey = KeyStore.Title;
                 // }
-                // else if (command == "Width" || command == "width") {
+                // else if (command === "Width" || command === "width") {
                 //     this._fieldKey = KeyStore.Width;
                 // }
                 this._title = "changed"
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 33759d38a..74f868c11 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -62,7 +62,7 @@ export class Main extends React.Component {
 
     @computed private get mainContainer(): Document | undefined {
         let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document);
-        return doc == FieldWaiting ? undefined : doc;
+        return doc === FieldWaiting ? undefined : doc;
     }
 
     private set mainContainer(doc: Document | undefined) {
@@ -84,7 +84,7 @@ export class Main extends React.Component {
         configure({ enforceActions: "observed" });
         if (window.location.pathname !== RouteStore.home) {
             let pathname = window.location.pathname.split("/");
-            if (pathname.length > 1 && pathname[pathname.length - 2] == 'doc') {
+            if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
                 CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
             }
         };
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index ee752a2ca..7a5ab6e3c 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -65,13 +65,13 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
         let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
-        for (let i = 0; i < data.length; i++) {
-            if (this.createsCycle(data[i], containerDocument))
+        for (const doc of data) {
+            if (this.createsCycle(doc, containerDocument))
                 return true;
         }
         let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
-        for (let i = 0; i < annots.length; i++) {
-            if (this.createsCycle(annots[i], containerDocument))
+        for (const annot of annots) {
+            if (this.createsCycle(annot, containerDocument))
                 return true;
         }
         for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1b0ad0bee..2380709d2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -101,12 +101,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             newRow.addChild(newContentItem, undefined, true);
             newRow.addChild(collayout, 0, true);
 
-            collayout.config["width"] = 50;
-            newContentItem.config["width"] = 50;
+            collayout.config.width = 50;
+            newContentItem.config.width = 50;
         }
         if (minimize) {
-            newContentItem.config["width"] = 10;
-            newContentItem.config["height"] = 10;
+            newContentItem.config.width = 10;
+            newContentItem.config.height = 10;
         }
         newContentItem.callDownwards('_$init');
         this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
@@ -124,7 +124,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 this._goldenLayout = new GoldenLayout(JSON.parse(config));
             }
             else {
-                if (config == JSON.stringify(this._goldenLayout.toConfig()))
+                if (config === JSON.stringify(this._goldenLayout.toConfig()))
                     return;
                 try {
                     this._goldenLayout.unbind('itemDropped', this.itemDropped);
@@ -154,7 +154,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             reaction(
                 () => this.props.Document.GetText(KeyStore.Data, ""),
                 () => {
-                    if (!this._goldenLayout || this._ignoreStateChange != JSON.stringify(this._goldenLayout.toConfig())) {
+                    if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
                         setTimeout(() => this.setupGoldenLayout(), 1);
                     }
                     this._ignoreStateChange = "";
@@ -193,7 +193,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         var className = (e.target as any).className;
-        if ((className == "lm_title" || className == "lm_tab lm_active") && (e.ctrlKey || e.altKey)) {
+        if ((className === "lm_title" || className === "lm_tab lm_active") && (e.ctrlKey || e.altKey)) {
             e.stopPropagation();
             e.preventDefault();
             let docid = (e.target as any).DashDocId;
@@ -209,7 +209,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         })
             }));
         }
-        if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
+        if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
             this._flush = true;
         }
         if (this.props.active()) {
@@ -227,12 +227,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this.stateChanged();
     }
     tabCreated = (tab: any) => {
-        if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type != "stack") {
-            if (tab.titleElement[0].textContent.indexOf("-waiting") != -1) {
+        if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+            if (tab.titleElement[0].textContent.indexOf("-waiting") !== -1) {
                 Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => {
-                    if (f != undefined && f instanceof Document) {
+                    if (f !== undefined && f instanceof Document) {
                         f.GetTAsync(KeyStore.Title, TextField, (tfield) => {
-                            if (tfield != undefined) {
+                            if (tfield !== undefined) {
                                 tab.titleElement[0].textContent = f.Title;
                             }
                         })
@@ -296,9 +296,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
     }
 
-    private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); }
-    private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); }
-    private _contentScaling = () => { return this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); }
+    private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth)
+    private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight)
+    private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth)
 
     ScreenToLocalTransform = () => {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 14ed70b8c..1f2e71f6e 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -33,7 +33,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
     }
 
     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
+        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: "PDFOptions", event: () => { } });
         }
     }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9dade94f1..3817d5333 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -151,7 +151,7 @@ export class CollectionSchemaView extends CollectionViewBase {
             }),
             style: {
                 background: rowInfo.index === this._selectedIndex ? "lightGray" : "white",
-                //color: rowInfo.index == this._selectedIndex ? "white" : "black"
+                //color: rowInfo.index === this._selectedIndex ? "white" : "black"
             }
         };
     }
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 3ab6db5ef..afadfeefb 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -61,7 +61,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
     @action
     updateTimecode = () => {
         if (this._player) {
-            if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero != -1) {
+            if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) {
                 this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero;
                 (this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1;
             } else {
@@ -103,7 +103,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
     }
 
     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
+        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: "VideoOptions", event: () => { } });
         }
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5b4caf58d..1b0cfd57b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -28,7 +28,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document.Id != CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) })
             ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) })
             ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) })
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index b4784ae9c..3af3c5d63 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -91,11 +91,11 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
             let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
             if (sourceDoc) runInAction(() => {
                 let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
-                if (srcTarg && srcTarg != FieldWaiting) {
+                if (srcTarg && srcTarg !== FieldWaiting) {
                     let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]);
                     linkDocs.map(linkDoc => {
                         let targDoc = linkDoc.GetT(KeyStore.LinkedToDocs, Document);
-                        if (targDoc && targDoc != FieldWaiting) {
+                        if (targDoc && targDoc !== FieldWaiting) {
                             let dropdoc = targDoc.MakeDelegate();
                             de.data.droppedDocuments.push(dropdoc);
                             this.props.addDocument(dropdoc, false);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 19227f5e0..52e40d64a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -176,8 +176,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
             e.stopPropagation();
             e.preventDefault();
         } else {
-            // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
-            // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
+            // if (modes[e.deltaMode] === 'pixels') coefficient = 50;
+            // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
             let transform = this.getTransform();
 
             let deltaScale = (1 - (e.deltaY / coefficient));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 7022fd972..704db1d4a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -48,7 +48,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.buttons == 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) {
+        if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) {
             this._downX = this._lastX = e.pageX;
             this._downY = this._lastY = e.pageY;
             this._used = false;
@@ -63,7 +63,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         this._lastX = e.pageX;
         this._lastY = e.pageY;
         if (!e.cancelBubble) {
-            if (!this._used && e.buttons == 1 && !e.altKey && !e.metaKey &&
+            if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey &&
                 (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) {
                 this._visible = true;
             }
@@ -99,15 +99,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @action
     marqueeCommand = (e: KeyboardEvent) => {
-        if (e.key == "Backspace" || e.key == "Delete") {
+        if (e.key === "Backspace" || e.key === "Delete") {
             this.marqueeSelect().map(d => this.props.removeDocument(d));
             let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-            if (ink && ink != FieldWaiting) {
+            if (ink && ink !== FieldWaiting) {
                 this.marqueeInkDelete(ink.Data);
             }
             this.cleanupInteractions();
         }
-        if (e.key == "c") {
+        if (e.key === "c") {
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
                 this.props.removeDocument(d);
@@ -118,7 +118,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 return d;
             });
             let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-            let inkData = ink && ink != FieldWaiting ? ink.Data : undefined;
+            let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined;
             //setTimeout(() => {
             let newCollection = Documents.FreeformDocument(selected, {
                 x: bounds.left,
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
index 93c98f7b0..599461f85 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -33,7 +33,7 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        if (e.button == 0 && this.props.container.props.active()) {
+        if (e.button === 0 && this.props.container.props.active()) {
             document.removeEventListener("keypress", this.onKeyPress, false);
             this._showOnUp = true;
             this.DownX = e.pageX;
diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
index a2c7be1a8..e4f17940c 100644
--- a/src/client/views/nodes/Annotation.tsx
+++ b/src/client/views/nodes/Annotation.tsx
@@ -4,13 +4,13 @@ import { observer } from "mobx-react"
 import { observable, action } from 'mobx';
 import 'react-pdf/dist/Page/AnnotationLayer.css'
 
-interface IProps{
+interface IProps {
     Span: HTMLSpanElement;
-    X: number; 
-    Y: number; 
-    Highlights: any[]; 
-    Annotations: any[]; 
-    CurrAnno: any[]; 
+    X: number;
+    Y: number;
+    Highlights: any[];
+    Annotations: any[];
+    CurrAnno: any[];
 
 }
 
@@ -23,95 +23,95 @@ interface IProps{
  */
 @observer
 export class Annotation extends React.Component<IProps> {
-    
+
     /**
      * changes color of the span (highlighted section)
      */
-    onColorChange = (e:React.PointerEvent) => {
-        if (e.currentTarget.innerHTML == "r"){
+    onColorChange = (e: React.PointerEvent) => {
+        if (e.currentTarget.innerHTML === "r") {
             this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"
-        } else if (e.currentTarget.innerHTML == "b"){
+        } else if (e.currentTarget.innerHTML === "b") {
             this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"
-        } else if (e.currentTarget.innerHTML == "y"){
+        } else if (e.currentTarget.innerHTML === "y") {
             this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"
-        } else if (e.currentTarget.innerHTML == "g"){
+        } else if (e.currentTarget.innerHTML === "g") {
             this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"
         }
-      
+
     }
 
     /**
      * removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this
      */
     @action
-    onRemove = (e:any) => {
-        let index:number = -1; 
+    onRemove = (e: any) => {
+        let index: number = -1;
         //finding the highlight in the highlight array 
         this.props.Highlights.forEach((e) => {
-            for (let i = 0; i < e.spans.length; i++){
-                if (e.spans[i] == this.props.Span){
-                    index = this.props.Highlights.indexOf(e); 
-                    this.props.Highlights.splice(index, 1); 
+            for (const span of e.spans) {
+                if (span === this.props.Span) {
+                    index = this.props.Highlights.indexOf(e);
+                    this.props.Highlights.splice(index, 1);
                 }
             }
         })
 
         //removing from CurrAnno and Annotation array 
-        this.props.Annotations.splice(index, 1); 
+        this.props.Annotations.splice(index, 1);
         this.props.CurrAnno.pop()
-        
+
         //removing span from div
-        if(this.props.Span.parentElement){
-            let nodesArray = this.props.Span.parentElement.childNodes; 
+        if (this.props.Span.parentElement) {
+            let nodesArray = this.props.Span.parentElement.childNodes;
             nodesArray.forEach((e) => {
-                if (e == this.props.Span){
-                    if (this.props.Span.parentElement){   
+                if (e === this.props.Span) {
+                    if (this.props.Span.parentElement) {
                         this.props.Highlights.forEach((item) => {
-                            if (item == e){
-                                item.remove(); 
+                            if (item === e) {
+                                item.remove();
                             }
                         })
-                        e.remove();                   
+                        e.remove();
                     }
                 }
-            }) 
+            })
         }
-    
-        
+
+
     }
 
     render() {
         return (
-            <div 
-            style = {{
-            position: "absolute",
-            top: "20px", 
-            left: "0px",  
-            zIndex: 1, 
-            transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
-            
-            }}>
-                <div style = {{width:"200px", height:"50px", backgroundColor: "orange"}}>
+            <div
+                style={{
+                    position: "absolute",
+                    top: "20px",
+                    left: "0px",
+                    zIndex: 1,
+                    transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+
+                }}>
+                <div style={{ width: "200px", height: "50px", backgroundColor: "orange" }}>
                     <button
-                    style = {{borderRadius: "25px", width:"25%", height:"100%"}}
-                    onClick = {this.onRemove}
+                        style={{ borderRadius: "25px", width: "25%", height: "100%" }}
+                        onClick={this.onRemove}
                     >x</button>
-                    <div style = {{width:"75%", height: "100%" , display:"inline-block"}}>
-                        <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"red", borderRadius:"50%", color: "transparent"}}>r</button>
-                        <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"blue", borderRadius:"50%", color: "transparent"}}>b</button>
-                        <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"yellow", borderRadius:"50%", color:"transparent"}}>y</button>
-                        <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"green", borderRadius:"50%", color:"transparent"}}>g</button>
+                    <div style={{ width: "75%", height: "100%", display: "inline-block" }}>
+                        <button onPointerDown={this.onColorChange} style={{ backgroundColor: "red", borderRadius: "50%", color: "transparent" }}>r</button>
+                        <button onPointerDown={this.onColorChange} style={{ backgroundColor: "blue", borderRadius: "50%", color: "transparent" }}>b</button>
+                        <button onPointerDown={this.onColorChange} style={{ backgroundColor: "yellow", borderRadius: "50%", color: "transparent" }}>y</button>
+                        <button onPointerDown={this.onColorChange} style={{ backgroundColor: "green", borderRadius: "50%", color: "transparent" }}>g</button>
                     </div>
-                    
+
                 </div>
-                <div  style = {{width:"200px", height:"200"}}>
-                    <textarea style = {{width: "100%", height: "100%"}}
-                    defaultValue = "Enter Text Here..."
-                   
+                <div style={{ width: "200px", height: "200" }}>
+                    <textarea style={{ width: "100%", height: "100%" }}
+                        defaultValue="Enter Text Here..."
+
                     ></textarea>
                 </div>
             </div>
-          
+
         );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 0ce991485..1bd934c25 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -29,7 +29,7 @@ export class AudioBox extends React.Component<FieldViewProps> {
 
     render() {
         let field = this.props.Document.Get(this.props.fieldKey)
-        let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
+        let path = field === FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
             field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
 
         return (
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 1c9155dce..34cc326aa 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -77,7 +77,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         }
         for (const key of this.layoutFields) {
             let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+            bindings[key.Name] = field && field !== FieldWaiting ? field.GetValue() : field;
         }
         return bindings;
     }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9b067aeeb..9958a7c5a 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -32,10 +32,10 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
-        if (e.key == 'Enter') {
+        if (e.key === 'Enter') {
             if (this._keyInput && this._valueInput) {
                 let doc = this.props.Document.GetT(KeyStore.Data, Document);
-                if (!doc || doc == FieldWaiting) {
+                if (!doc || doc === FieldWaiting) {
                     return
                 }
                 let realDoc = doc;
@@ -70,7 +70,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
     createTable = () => {
         let doc = this.props.Document.GetT(KeyStore.Data, Document);
-        if (!doc || doc == FieldWaiting) {
+        if (!doc || doc === FieldWaiting) {
             return <tr><td>Loading...</td></tr>
         }
         let realDoc = doc;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index e81f8fec7..4791d6029 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -49,7 +49,7 @@ export class LinkBox extends React.Component<Props> {
                 } else if (contextDoc instanceof Document) {
                     this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => {
                         contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => {
-                            if (pfield != cfield)
+                            if (pfield !== cfield)
                                 contextDoc.SetNumber(KeyStore.CurPage, pfield.Data);
                             let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
                             if (contextView) {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 5eeb40772..6c2d24630 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -24,7 +24,7 @@ export class LinkMenu extends React.Component<Props> {
     renderLinkItems(links: Document[], key: Key, type: string) {
         return links.map(link => {
             let doc = link.GetT(key, Document);
-            if (doc && doc != FieldWaiting) {
+            if (doc && doc !== FieldWaiting) {
                 return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />
             }
         })
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b4590df34..a08b320e8 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -34,7 +34,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
             var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
             var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
             var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
-            if (!nativeHeight && newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) {
+            if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
                 this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
                 this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight);
             }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 92e2fabd4..c1d389001 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -20,7 +20,7 @@ export class WebBox extends React.Component<FieldViewProps> {
 
     render() {
         let field = this.props.Document.Get(this.props.fieldKey);
-        let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+        let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
             field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
 
         let content = this.html ?
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
index 8864471ae..252a5b74e 100644
--- a/src/fields/AudioField.ts
+++ b/src/fields/AudioField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
 
 export class AudioField extends BasicField<URL> {
     constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data == undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
+        super(data === undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
     }
 
     toString(): string {
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
index a92c4a236..3083a1937 100644
--- a/src/fields/BasicField.ts
+++ b/src/fields/BasicField.ts
@@ -46,7 +46,7 @@ export abstract class BasicField<T> extends Field {
 
     @action
     TrySetValue(value: any): boolean {
-        if (typeof value == typeof this.data) {
+        if (typeof value === typeof this.data) {
             this.Data = value;
             return true;
         }
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
index a9ece7d7b..ef616b2ad 100644
--- a/src/fields/ImageField.ts
+++ b/src/fields/ImageField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
 
 export class ImageField extends BasicField<URL> {
     constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
+        super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
     }
 
     toString(): string {
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
index c4008bd12..815a3df73 100644
--- a/src/fields/ListField.ts
+++ b/src/fields/ListField.ts
@@ -26,7 +26,7 @@ export class ListField<T extends Field> extends BasicField<T[]> {
         }
         this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
             this.updateProxies()
-            if (change.type == "splice") {
+            if (change.type === "splice") {
                 UndoManager.AddEvent({
                     undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed),
                     redo: () => this.Data.splice(change.index, change.removedCount, ...change.added)
@@ -57,8 +57,8 @@ export class ListField<T extends Field> extends BasicField<T[]> {
     }
     private arraysEqual(a: any[], b: any[]) {
         if (a === b) return true;
-        if (a == null || b == null) return false;
-        if (a.length != b.length) return false;
+        if (a === null || b === null) return false;
+        if (a.length !== b.length) return false;
 
         // If you don't care about the order of the elements inside
         // the array, you should sort both arrays here.
@@ -79,9 +79,9 @@ export class ListField<T extends Field> extends BasicField<T[]> {
                 var added = this.data.length < this._proxies.length;
                 var deleted = this.data.length > this._proxies.length;
                 for (let i = 0; i < dataids.length && added; i++)
-                    added = proxies.indexOf(dataids[i]) != -1;
+                    added = proxies.indexOf(dataids[i]) !== -1;
                 for (let i = 0; i < this._proxies.length && deleted; i++)
-                    deleted = dataids.indexOf(proxies[i]) != -1;
+                    deleted = dataids.indexOf(proxies[i]) !== -1;
 
                 this._processingServerUpdate = true;
                 for (let i = 0; i < proxies.length && added; i++) {
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
index b6625387e..436c1cf2b 100644
--- a/src/fields/PDFField.ts
+++ b/src/fields/PDFField.ts
@@ -7,7 +7,7 @@ import { Types } from "../server/Message";
 
 export class PDFField extends BasicField<URL> {
     constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
+        super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
     }
 
     toString(): string {
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
index 626e4ec83..992cc1641 100644
--- a/src/fields/VideoField.ts
+++ b/src/fields/VideoField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
 
 export class VideoField extends BasicField<URL> {
     constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data == undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
+        super(data === undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
     }
 
     toString(): string {
diff --git a/src/server/index.ts b/src/server/index.ts
index ce8bbefe8..f60e6e293 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -143,7 +143,7 @@ addSecureRoute(
     Method.GET,
     (user, res, req) => {
         let detector = new mobileDetect(req.headers['user-agent'] || "");
-        if (detector.mobile() != null) {
+        if (detector.mobile() !== null) {
             res.sendFile(path.join(__dirname, '../../deploy/mobile/image.html'));
         } else {
             res.sendFile(path.join(__dirname, '../../deploy/index.html'));
-- 
cgit v1.2.3-70-g09d2


From 5e086920bf97297a02bcd38faea56454c2220279 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 8 Apr 2019 22:45:22 -0400
Subject: Enabled semi-colon and braces linter rule

---
 src/Utils.ts                                       | 12 +--
 src/client/Server.ts                               | 35 ++++----
 src/client/SocketStub.ts                           | 24 +++---
 src/client/documents/Documents.ts                  | 38 +++++----
 src/client/northstar/core/BaseObject.ts            |  4 +-
 .../northstar/core/attribute/AttributeModel.ts     |  6 +-
 .../core/attribute/AttributeTransformationModel.ts | 17 ++--
 .../core/attribute/CalculatedAttributeModel.ts     |  2 +-
 .../northstar/core/brusher/IBaseBrushable.ts       |  4 +-
 src/client/northstar/core/brusher/IBaseBrusher.ts  |  2 +-
 src/client/northstar/core/filter/FilterModel.ts    |  2 +-
 .../northstar/core/filter/IBaseFilterConsumer.ts   |  4 +-
 .../northstar/core/filter/IBaseFilterProvider.ts   |  2 +-
 .../northstar/core/filter/ValueComparision.ts      | 16 ++--
 src/client/northstar/dash-fields/HistogramField.ts |  2 +-
 .../dash-nodes/HistogramBinPrimitiveCollection.ts  | 12 +--
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 24 +++---
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 40 +++++----
 .../dash-nodes/HistogramLabelPrimitives.tsx        | 15 ++--
 src/client/northstar/manager/Gateway.ts            |  2 +-
 src/client/northstar/model/ModelExtensions.ts      | 18 ++--
 src/client/northstar/model/ModelHelpers.ts         |  8 +-
 .../model/binRanges/AlphabeticVisualBinRange.ts    |  4 +-
 .../model/binRanges/DateTimeVisualBinRange.ts      |  4 +-
 .../model/binRanges/NominalVisualBinRange.ts       |  4 +-
 .../model/binRanges/QuantitativeVisualBinRange.ts  |  2 +-
 .../northstar/model/binRanges/VisualBinRange.ts    |  2 +-
 .../model/binRanges/VisualBinRangeHelper.ts        | 14 +--
 src/client/northstar/operations/BaseOperation.ts   |  9 +-
 .../northstar/operations/HistogramOperation.ts     |  2 +-
 src/client/northstar/utils/ArrayUtil.ts            |  2 +-
 src/client/northstar/utils/Extensions.ts           |  6 +-
 src/client/northstar/utils/GeometryUtil.ts         | 34 ++++----
 src/client/northstar/utils/MathUtil.ts             | 38 +++++----
 src/client/northstar/utils/SizeConverter.ts        | 16 ++--
 src/client/northstar/utils/StyleContants.ts        |  2 +-
 src/client/northstar/utils/Utils.ts                |  8 +-
 src/client/util/DocumentManager.ts                 | 12 +--
 src/client/util/DragManager.ts                     |  5 +-
 src/client/util/RichTextSchema.tsx                 | 42 ++++-----
 src/client/util/Scripting.ts                       | 10 +--
 src/client/util/ScrollBox.tsx                      |  4 +-
 src/client/util/SelectionManager.ts                |  4 +-
 src/client/util/TooltipTextMenu.tsx                | 38 ++++-----
 src/client/util/Transform.ts                       | 20 ++---
 src/client/util/TypedEvent.ts                      |  2 +-
 src/client/util/UndoManager.ts                     | 24 +++---
 src/client/views/ContextMenu.tsx                   | 14 +--
 src/client/views/ContextMenuItem.tsx               |  4 +-
 src/client/views/DocumentDecorations.tsx           | 99 +++++++++++-----------
 src/client/views/EditableView.tsx                  | 10 +--
 src/client/views/InkingCanvas.tsx                  | 15 ++--
 src/client/views/InkingControl.tsx                 | 12 +--
 src/client/views/InkingStroke.tsx                  |  6 +-
 src/client/views/Main.tsx                          | 55 ++++++------
 .../views/collections/CollectionBaseView.tsx       | 34 +++++---
 .../views/collections/CollectionDockingView.tsx    | 37 ++++----
 src/client/views/collections/CollectionPDFView.tsx |  6 +-
 .../views/collections/CollectionSchemaView.tsx     | 39 ++++-----
 src/client/views/collections/CollectionSubView.tsx | 41 ++++-----
 .../views/collections/CollectionTreeView.tsx       | 14 +--
 .../views/collections/CollectionVideoView.tsx      |  6 +-
 src/client/views/collections/CollectionView.tsx    | 20 ++---
 .../CollectionFreeFormLinkView.tsx                 |  2 +-
 .../CollectionFreeFormLinksView.tsx                | 18 ++--
 .../CollectionFreeFormRemoteCursors.tsx            |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 ++++----
 .../collections/collectionFreeForm/MarqueeView.tsx | 15 ++--
 .../collectionFreeForm/PreviewCursor.tsx           |  5 +-
 src/client/views/nodes/Annotation.tsx              | 22 ++---
 src/client/views/nodes/AudioBox.tsx                | 12 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 12 +--
 src/client/views/nodes/DocumentContentsView.tsx    |  6 +-
 src/client/views/nodes/DocumentView.tsx            | 35 ++++----
 src/client/views/nodes/FieldView.tsx               | 29 ++++---
 src/client/views/nodes/FormattedTextBox.tsx        | 17 ++--
 src/client/views/nodes/ImageBox.tsx                | 12 +--
 src/client/views/nodes/KeyValueBox.tsx             | 18 ++--
 src/client/views/nodes/KeyValuePair.tsx            | 14 +--
 src/client/views/nodes/LinkBox.tsx                 | 17 ++--
 src/client/views/nodes/LinkEditor.tsx              |  6 +-
 src/client/views/nodes/LinkMenu.tsx                | 10 +--
 src/client/views/nodes/PDFBox.tsx                  | 56 ++++++------
 src/client/views/nodes/Sticky.tsx                  |  6 +-
 src/client/views/nodes/VideoBox.tsx                | 10 +--
 src/client/views/nodes/WebBox.tsx                  |  6 +-
 src/debug/Test.tsx                                 |  8 +-
 src/debug/Viewer.tsx                               | 48 +++++------
 src/fields/AudioField.ts                           |  2 +-
 src/fields/BasicField.ts                           |  6 +-
 src/fields/Document.ts                             | 25 +++---
 src/fields/DocumentReference.ts                    |  2 +-
 src/fields/Field.ts                                |  2 +-
 src/fields/HtmlField.ts                            |  2 +-
 src/fields/ImageField.ts                           |  2 +-
 src/fields/InkField.ts                             |  2 +-
 src/fields/Key.ts                                  |  6 +-
 src/fields/KeyStore.ts                             |  3 +-
 src/fields/ListField.ts                            | 31 ++++---
 src/fields/NumberField.ts                          |  4 +-
 src/fields/PDFField.ts                             |  4 +-
 src/fields/RichTextField.ts                        |  2 +-
 src/fields/TextField.ts                            |  4 +-
 src/fields/TupleField.ts                           |  6 +-
 src/fields/VideoField.ts                           |  2 +-
 src/fields/WebField.ts                             |  2 +-
 src/mobile/ImageUpload.tsx                         | 14 +--
 src/server/Client.ts                               |  4 +-
 src/server/authentication/config/passport.ts       |  6 +-
 .../authentication/controllers/WorkspacesMenu.tsx  |  2 +-
 .../authentication/controllers/user_controller.ts  | 20 ++---
 .../authentication/models/current_user_utils.ts    |  8 +-
 src/server/authentication/models/user_model.ts     |  2 +-
 src/server/database.ts                             | 34 ++++----
 src/server/index.ts                                | 76 ++++++++---------
 test/test.ts                                       |  6 +-
 tslint.json                                        | 16 ++--
 117 files changed, 901 insertions(+), 826 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 8bd7f2f5c..b0e66787e 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -6,16 +6,16 @@ import { Message, Types } from './server/Message';
 export class Utils {
 
     public static GenerateGuid(): string {
-        return v4()
+        return v4();
     }
 
     public static GenerateDeterministicGuid(seed: string): string {
-        return v5(seed, v5.URL)
+        return v5(seed, v5.URL);
     }
 
     public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } {
         if (!ele) {
-            return { scale: 1, translateX: 1, translateY: 1 }
+            return { scale: 1, translateX: 1, translateY: 1 };
         }
         const rect = ele.getBoundingClientRect();
         const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
@@ -55,8 +55,8 @@ export class Utils {
         return (args: any) => {
             this.log(prefix, messageName, args, true);
             func(args);
-        }
-    };
+        };
+    }
 
     public static Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) {
         this.log("Emit", message.Name, args, false);
@@ -81,7 +81,7 @@ export class Utils {
     public static AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
         socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
             this.log('S receiving', message.Name, arg, true);
-            handler([arg, this.loggingCallback('S sending', fn, message.Name)])
+            handler([arg, this.loggingCallback('S sending', fn, message.Name)]);
         });
     }
 }
diff --git a/src/client/Server.ts b/src/client/Server.ts
index 45c7144ca..857101a33 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -1,7 +1,7 @@
-import { Key } from "../fields/Key"
+import { Key } from "../fields/Key";
 import { ObservableMap, action, reaction } from "mobx";
-import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field"
-import { Document } from "../fields/Document"
+import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field";
+import { Document } from "../fields/Document";
 import { SocketStub, FieldMap } from "./SocketStub";
 import * as OpenSocket from 'socket.io-client';
 import { Utils } from "./../Utils";
@@ -10,7 +10,7 @@ import { MessageStore, Types } from "./../server/Message";
 export class Server {
     public static ClientFieldsCached: ObservableMap<FieldId, Field | FIELD_WAITING> = new ObservableMap();
     static Socket: SocketIOClient.Socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`);
-    static GUID: string = Utils.GenerateGuid()
+    static GUID: string = Utils.GenerateGuid();
 
 
     // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached).
@@ -25,15 +25,16 @@ export class Server {
                 this.ClientFieldsCached.set(fieldid, FieldWaiting);
                 SocketStub.SEND_FIELD_REQUEST(fieldid, action((field: Field | undefined) => {
                     let cached = this.ClientFieldsCached.get(fieldid);
-                    if (cached !== FieldWaiting)
+                    if (cached !== FieldWaiting) {
                         cb(cached);
+                    }
                     else {
                         if (field) {
                             this.ClientFieldsCached.set(fieldid, field);
                         } else {
-                            this.ClientFieldsCached.delete(fieldid)
+                            this.ClientFieldsCached.delete(fieldid);
                         }
-                        cb(field)
+                        cb(field);
                     }
                 }));
             } else if (cached !== FieldWaiting) {
@@ -42,12 +43,12 @@ export class Server {
                 reaction(() => this.ClientFieldsCached.get(fieldid),
                     (field, reaction) => {
                         if (field !== FieldWaiting) {
-                            reaction.dispose()
-                            cb(field)
+                            reaction.dispose();
+                            cb(field);
                         }
-                    })
+                    });
             }
-        }
+        };
         if (callback) {
             fn(callback);
         } else {
@@ -79,15 +80,15 @@ export class Server {
                     let field = fields[id];
                     if (field) {
                         if (!(this.ClientFieldsCached.get(field.Id) instanceof Field)) {
-                            this.ClientFieldsCached.set(field.Id, field)
+                            this.ClientFieldsCached.set(field.Id, field);
                         } else {
-                            throw new Error("we shouldn't be trying to replace things that are already in the cache")
+                            throw new Error("we shouldn't be trying to replace things that are already in the cache");
                         }
                     } else {
                         if (this.ClientFieldsCached.get(id) === FieldWaiting) {
                             this.ClientFieldsCached.delete(id);
                         } else {
-                            throw new Error("we shouldn't be trying to replace things that are already in the cache")
+                            throw new Error("we shouldn't be trying to replace things that are already in the cache");
                         }
                     }
                 }
@@ -99,9 +100,9 @@ export class Server {
                                 let realField = field as Field;
                                 existingFields[realField.Id] = realField;
                             }
-                            cb({ ...fields, ...existingFields })
+                            cb({ ...fields, ...existingFields });
                         }
-                    }, { fireImmediately: true })
+                    }, { fireImmediately: true });
             }));
         };
         if (callback) {
@@ -139,7 +140,7 @@ export class Server {
 
     public static UpdateField(field: Field) {
         if (!this.ClientFieldsCached.has(field.Id)) {
-            this.ClientFieldsCached.set(field.Id, field)
+            this.ClientFieldsCached.set(field.Id, field);
         }
         SocketStub.SEND_SET_FIELD(field);
     }
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
index c3cd8bee6..257973e3d 100644
--- a/src/client/SocketStub.ts
+++ b/src/client/SocketStub.ts
@@ -1,7 +1,7 @@
-import { Key } from "../fields/Key"
-import { Field, FieldId, Opt } from "../fields/Field"
+import { Key } from "../fields/Key";
+import { Field, FieldId, Opt } from "../fields/Field";
 import { ObservableMap } from "mobx";
-import { Document } from "../fields/Document"
+import { Document } from "../fields/Document";
 import { MessageStore, DocumentTransfer } from "../server/Message";
 import { Utils } from "../Utils";
 import { Server } from "./Server";
@@ -37,7 +37,7 @@ export class SocketStub {
         // document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key.Id, (f as Field).Id));
 
         console.log("sending " + document.Title);
-        Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()))
+        Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()));
     }
 
     public static SEND_FIELD_REQUEST(fieldid: FieldId): Promise<Opt<Field>>;
@@ -50,12 +50,12 @@ export class SocketStub {
                 } else {
                     cb(undefined);
                 }
-            })
-        }
+            });
+        };
         if (callback) {
             fn(callback);
         } else {
-            return new Promise(fn)
+            return new Promise(fn);
         }
     }
 
@@ -65,7 +65,7 @@ export class SocketStub {
             for (let field of fields) {
                 fieldMap[field._id] = ServerUtils.FromJson(field);
             }
-            callback(fieldMap)
+            callback(fieldMap);
         });
     }
 
@@ -78,8 +78,9 @@ export class SocketStub {
 
         // server updates its document to hold a proxy mapping from key => fieldId
         var document = this.FieldStore.get(doc.Id) as Document;
-        if (document)
+        if (document) {
             document._proxies.set(key.Id, value.Id);
+        }
 
         // server adds the field to its repository of fields
         this.FieldStore.set(value.Id, value);
@@ -93,8 +94,9 @@ export class SocketStub {
 
         // Server removes the field id from the document's list of field proxies
         var document = this.FieldStore.get(doc.Id) as Document;
-        if (document)
+        if (document) {
             document._proxies.delete(key.Id);
+        }
     }
 
     public static SEND_SET_FIELD(field: Field) {
@@ -103,6 +105,6 @@ export class SocketStub {
         // ...SOCKET(SET_FIELD, field id, serialized field value)
 
         // Server updates the value of the field in its fieldstore
-        Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson())
+        Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson());
     }
 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 613b94abd..72e6e57ab 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -64,7 +64,7 @@ export namespace Documents {
     const webProtoId = "webProto";
     const collProtoId = "collectionProto";
     const kvpProtoId = "kvpProto";
-    const videoProtoId = "videoProto"
+    const videoProtoId = "videoProto";
     const audioProtoId = "audioProto";
 
     export function initProtos(): Promise<void> {
@@ -102,7 +102,7 @@ export namespace Documents {
         if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
         if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
         if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
-        return doc
+        return doc;
     }
 
     function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
@@ -110,10 +110,12 @@ export namespace Documents {
     }
     function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
         var deleg = doc.MakeDelegate(id);
-        if (value instanceof Document)
-            deleg.Set(KeyStore.Data, value)
-        else
+        if (value instanceof Document) {
+            deleg.Set(KeyStore.Data, value);
+        }
+        else {
             deleg.SetData(KeyStore.Data, value[0], value[1]);
+        }
         return assignOptions(deleg, options);
     }
 
@@ -134,7 +136,7 @@ export namespace Documents {
     function CreateTextPrototype(): Document {
         let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
             { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
-        return textProto
+        return textProto;
     }
     function CreatePdfPrototype(): Document {
         let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
@@ -168,7 +170,7 @@ export namespace Documents {
     }
     function CreateAudioPrototype(): Document {
         let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
         return audioProto;
     }
 
@@ -205,22 +207,22 @@ export namespace Documents {
         return assignToDelegate(SetInstanceOptions(webProto, options, [html, HtmlField]).MakeDelegate(), options);
     }
     export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
-        return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options)
+        return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options);
     }
     export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string, makePrototype: boolean = true) {
         if (!makePrototype) {
-            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id)
+            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id);
         }
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options);
     }
     export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options);
     }
     export function TreeDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options);
     }
     export function DockDocument(config: string, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options)
+        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options);
     }
 
     export function CaptionDocument(doc: Document) {
@@ -240,13 +242,13 @@ export namespace Documents {
             <div style="position:relative; height:15%; text-align:center; ">`
             + FormattedTextBox.LayoutString("CaptionKey") +
             `</div> 
-        </div>` };
+        </div>`; }
     export function FixedCaption(fieldName: string = "Caption") {
         return `<div style="position:absolute; height:30px; bottom:0; width:100%">
             <div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
             + FormattedTextBox.LayoutString(fieldName + "Key") +
             `</div> 
-        </div>` };
+        </div>`; }
 
     function OuterCaption() {
         return (`
@@ -258,7 +260,7 @@ export namespace Documents {
         <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
     </div>
 </div>       
-        `)
+        `);
     }
     function InnerCaption() {
         return (`
@@ -270,7 +272,7 @@ export namespace Documents {
             <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
-            `)
+            `);
     }
 
     /*
@@ -293,6 +295,6 @@ export namespace Documents {
             <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
-            `)
+            `);
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
index f1761e643..ed3818071 100644
--- a/src/client/northstar/core/BaseObject.ts
+++ b/src/client/northstar/core/BaseObject.ts
@@ -1,5 +1,5 @@
-import { IEquatable } from '../utils/IEquatable'
-import { IDisposable } from '../utils/IDisposable'
+import { IEquatable } from '../utils/IEquatable';
+import { IDisposable } from '../utils/IDisposable';
 
 export class BaseObject implements IEquatable, IDisposable {
 
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
index 230bfecc8..c89b1617c 100644
--- a/src/client/northstar/core/attribute/AttributeModel.ts
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -1,5 +1,5 @@
-import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea'
-import { BaseObject } from '../BaseObject'
+import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea';
+import { BaseObject } from '../BaseObject';
 import { observable } from "mobx";
 
 export abstract class AttributeModel extends BaseObject {
@@ -93,7 +93,7 @@ export class BackendAttributeModel extends AttributeModel {
     }
 
     public get DisplayName(): string {
-        return this._displayName.ReplaceAll("_", " ");;
+        return this._displayName.ReplaceAll("_", " ");
     }
 
     public get CodeName(): string {
diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
index f50b78d51..66485183b 100644
--- a/src/client/northstar/core/attribute/AttributeTransformationModel.ts
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -1,4 +1,4 @@
-;
+
 import { computed, observable } from "mobx";
 import { AggregateFunction } from "../../model/idea/idea";
 import { AttributeModel } from "./AttributeModel";
@@ -20,16 +20,21 @@ export class AttributeTransformationModel implements IEquatable {
         if (this.AggregateFunction === AggregateFunction.Count) {
             return "count";
         }
-        if (this.AggregateFunction === AggregateFunction.Avg)
+        if (this.AggregateFunction === AggregateFunction.Avg) {
             displayName = "avg(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.Max)
+        }
+        else if (this.AggregateFunction === AggregateFunction.Max) {
             displayName = "max(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.Min)
+ }
+        else if (this.AggregateFunction === AggregateFunction.Min) {
             displayName = "min(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.Sum)
+ }
+        else if (this.AggregateFunction === AggregateFunction.Sum) {
             displayName = "sum(" + displayName + ")";
-        else if (this.AggregateFunction === AggregateFunction.SumE)
+ }
+        else if (this.AggregateFunction === AggregateFunction.SumE) {
             displayName = "sumE(" + displayName + ")";
+ }
 
         return displayName;
     }
diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
index 0b8e0d12c..a197c1305 100644
--- a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -1,5 +1,5 @@
 import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel";
-import { DataType, VisualizationHint } from '../../model/idea/idea'
+import { DataType, VisualizationHint } from '../../model/idea/idea';
 
 export class CalculatedAttributeManager {
     public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>();
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
index 99a36636f..c46db4d22 100644
--- a/src/client/northstar/core/brusher/IBaseBrushable.ts
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -1,6 +1,6 @@
-import { PIXIPoint } from '../../utils/MathUtil'
+import { PIXIPoint } from '../../utils/MathUtil';
 import { IEquatable } from '../../utils/IEquatable';
-import { Document } from '../../../../fields/Document'
+import { Document } from '../../../../fields/Document';
 
 export interface IBaseBrushable<T> extends IEquatable {
     BrusherModels: Array<Document>;
diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts
index d7ae65464..d2de6ed62 100644
--- a/src/client/northstar/core/brusher/IBaseBrusher.ts
+++ b/src/client/northstar/core/brusher/IBaseBrusher.ts
@@ -1,4 +1,4 @@
-import { PIXIPoint } from '../../utils/MathUtil'
+import { PIXIPoint } from '../../utils/MathUtil';
 import { IEquatable } from '../../utils/IEquatable';
 
 
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index 20fca77f5..e2ba3f652 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -15,7 +15,7 @@ export class FilterModel {
 
     public Equals(other: FilterModel): boolean {
         if (!Utils.EqualityHelper(this, other)) return false;
-        if (!this.isSame(this.ValueComparisons, (other as FilterModel).ValueComparisons)) return false;
+        if (!this.isSame(this.ValueComparisons, (other).ValueComparisons)) return false;
         return true;
     }
 
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
index 93f66a154..59d7adf4c 100644
--- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -1,5 +1,5 @@
-import { FilterOperand } from '../filter/FilterOperand'
-import { IEquatable } from '../../utils/IEquatable'
+import { FilterOperand } from '../filter/FilterOperand';
+import { IEquatable } from '../../utils/IEquatable';
 import { Document } from "../../../../fields/Document";
 
 export interface IBaseFilterConsumer extends IEquatable {
diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts
index d082bfe12..fc3301b11 100644
--- a/src/client/northstar/core/filter/IBaseFilterProvider.ts
+++ b/src/client/northstar/core/filter/IBaseFilterProvider.ts
@@ -1,4 +1,4 @@
-import { FilterModel } from '../filter/FilterModel'
+import { FilterModel } from '../filter/FilterModel';
 
 export interface IBaseFilterProvider {
     FilterModels: Array<FilterModel>;
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
index 1a3e461f5..80b1242a9 100644
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -1,5 +1,5 @@
-import { Predicate } from '../../model/idea/idea'
-import { Utils } from '../../utils/Utils'
+import { Predicate } from '../../model/idea/idea';
+import { Utils } from '../../utils/Utils';
 import { AttributeModel } from '../attribute/AttributeModel';
 
 export class ValueComparison {
@@ -15,15 +15,19 @@ export class ValueComparison {
     }
 
     public Equals(other: Object): boolean {
-        if (!Utils.EqualityHelper(this, other))
+        if (!Utils.EqualityHelper(this, other)) {
             return false;
-        if (this.Predicate !== (other as ValueComparison).Predicate)
+        }
+        if (this.Predicate !== (other as ValueComparison).Predicate) {
             return false;
+        }
         let isComplex = (typeof this.Value === "object");
-        if (!isComplex && this.Value !== (other as ValueComparison).Value)
+        if (!isComplex && this.Value !== (other as ValueComparison).Value) {
             return false;
-        if (isComplex && !this.Value.Equals((other as ValueComparison).Value))
+        }
+        if (isComplex && !this.Value.Equals((other as ValueComparison).Value)) {
             return false;
+        }
         return true;
     }
 
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index fa2a9c008..6abde4677 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -41,7 +41,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
 
             data: this.toString(),
             _id: this.Id
-        }
+        };
     }
 
     @action
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
index cdae90c8b..6291ec1fc 100644
--- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -1,4 +1,4 @@
-import React = require("react")
+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";
@@ -29,7 +29,7 @@ export class HistogramBinPrimitiveCollection {
     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!; }
+    private get sizeConverter() { return this._histoBox.SizeConverter; }
     public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
     public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
 
@@ -99,13 +99,14 @@ export class HistogramBinPrimitiveCollection {
     private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
 
         let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
-        if (unNormalizedValue === undefined)
+        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))
+        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);
@@ -145,8 +146,9 @@ export class HistogramBinPrimitiveCollection {
             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)
+            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;
     }
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index ec9413649..7df59ef07 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import Measure from "react-measure";
@@ -25,7 +25,7 @@ import { StyleConstants } from "../utils/StyleContants";
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr); }
     private _dropXRef = React.createRef<HTMLDivElement>();
     private _dropYRef = React.createRef<HTMLDivElement>();
     private _dropXDisposer?: DragManager.DragDropDisposer;
@@ -92,15 +92,15 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         }
         reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
         reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
-        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40))
+        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 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)));
+                        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 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]);
@@ -109,10 +109,12 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     }
 
     componentWillUnmount() {
-        if (this._dropXDisposer)
+        if (this._dropXDisposer) {
             this._dropXDisposer();
-        if (this._dropYDisposer)
+        }
+        if (this._dropYDisposer) {
             this._dropYDisposer();
+        }
     }
 
     activateHistogramOperation(catalog?: Catalog) {
@@ -128,7 +130,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                             this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
                                 brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
                                 let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                                return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] }
+                                return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] };
                             }));
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
@@ -146,7 +148,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         let roff = this.SizeConverter.RightOffset;
         let boff = this.SizeConverter.BottomOffset;
         return (
-            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}>
+            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
                 {({ measureRef }) =>
                     <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}>
                         <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
@@ -168,7 +170,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                     </div>
                 }
             </Measure>
-        )
+        );
     }
 }
 
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index a78703247..4c5bdb14b 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { computed, observable, reaction, runInAction, trace, action } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
@@ -28,17 +28,18 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
     @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)
+        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 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));
+                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[])
+        }, [] as JSX.Element[]);
     }
 
     componentDidMount() {
@@ -47,8 +48,9 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
 
     private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
         let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
-        if (!rawAllBrushPrim)
-            return () => { }
+        if (!rawAllBrushPrim) {
+            return () => { };
+        }
         let allBrushPrim = rawAllBrushPrim;
         return () => runInAction(() => {
             if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
@@ -59,23 +61,25 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
                 this._selectedPrims.push(allBrushPrim);
                 this.histoOp.AddFilterModels([filterModel]);
             }
-        })
+        });
     }
 
     private renderGridLinesAndLabels(axis: number) {
         trace();
-        if (!this.props.HistoBox.SizeConverter.Initialized)
+        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)
+                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>
+        </svg>;
     }
 
     drawLine(xFrom: number, yFrom: number, width: number, height: number) {
@@ -89,9 +93,9 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         }
         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}`} />
+        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 = () => { }) {
         if (r.height < 0) {
@@ -102,11 +106,11 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
             r.x += r.width;
             r.width = -r.width;
         }
-        let transXpercent = `${r.x / this.renderDimension * 100}%`
-        let transYpercent = `${r.y / this.renderDimension * 100}%`
+        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() }}
+        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() {
@@ -118,6 +122,6 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
                 {this.barPrimitives}
                 {this.selectedPrimitives}
             </svg>
-        </div>
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index dd62e9146..5785fe838 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { action, computed, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
@@ -13,7 +13,7 @@ import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
 export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
     componentDidMount() {
         reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
-            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true });
+            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
     }
 
     @action
@@ -32,8 +32,9 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
     private renderGridLinesAndLabels(axis: number) {
         let sc = this.props.HistoBox.SizeConverter;
         let vb = this.props.HistoBox.VisualBinRanges;
-        if (!vb.length || !sc.Initialized)
+        if (!vb.length || !sc.Initialized) {
             return (null);
+        }
         let dim = (axis === 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
             (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
             sc.MaxLabelSizes[axis].coords[axis] + 5);
@@ -52,8 +53,8 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
                     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.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`
+                let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
+                let yPercent = axis === 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
 
                 prims.push(
                     <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
@@ -61,7 +62,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
                             {label}
                         </div>
                     </div>
-                )
+                );
             }
             return prims;
         }, [] as JSX.Element[]);
@@ -73,7 +74,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
         return <div className="histogramLabelPrimitives-container">
             {xaxislines}
             {yaxislines}
-        </div>
+        </div>;
     }
 
 }
\ No newline at end of file
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
index ba6fe2ad5..1c8d3fd73 100644
--- a/src/client/northstar/manager/Gateway.ts
+++ b/src/client/northstar/manager/Gateway.ts
@@ -1,4 +1,4 @@
-import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea"
+import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea";
 import { computed, observable, action } from "mobx";
 
 export class Gateway {
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
index e4bf77ed8..29f80d2d1 100644
--- a/src/client/northstar/model/ModelExtensions.ts
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -1,7 +1,7 @@
-import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea'
-import { Utils } from '../utils/Utils'
+import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea';
+import { Utils } from '../utils/Utils';
 
-import { FilterModel } from '../core/filter/FilterModel'
+import { FilterModel } from '../core/filter/FilterModel';
 
 (SingleDimensionAggregateParameters as any).prototype.Equals = function (other: Object) {
     if (!Utils.EqualityHelper(this, other)) return false;
@@ -9,13 +9,13 @@ import { FilterModel } from '../core/filter/FilterModel'
         (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 (<any>this).constructor.name === (<any>other).constructor.name &&
+        return (this).constructor.name === (<any>other).constructor.name &&
             this.rawName === other.rawName;
-    }
+    };
 }
 
 {
@@ -23,7 +23,7 @@ import { FilterModel } from '../core/filter/FilterModel'
         if (!Utils.EqualityHelper(this, other)) return false;
         if ((this as Solution).solutionId !== (other as Solution).solutionId) return false;
         return true;
-    }
+    };
 }
 
 {
@@ -35,7 +35,7 @@ import { FilterModel } from '../core/filter/FilterModel'
 
         if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
         return true;
-    }
+    };
 }
 
 {
@@ -44,5 +44,5 @@ import { FilterModel } from '../core/filter/FilterModel'
         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
index 1a58e6180..ac807b41f 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -64,7 +64,7 @@ export class ModelHelpers {
             if (aggParams) {
                 aggregateParameters.push(aggParams);
 
-                var margin = new MarginAggregateParameters()
+                var margin = new MarginAggregateParameters();
                 margin.aggregateFunction = agg.AggregateFunction;
                 margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
                 margin.distinctAttributeParameters = distinctAttributeParameters;
@@ -106,7 +106,7 @@ export class ModelHelpers {
                 {
                     rawName: am.CodeName,
                     visualizationHints: am.VisualizationHints,
-                    id: (am as BackendAttributeModel).Id
+                    id: (am).Id
                 });
         }
         else if (am instanceof CodeAttributeModel) {
@@ -114,11 +114,11 @@ export class ModelHelpers {
                 {
                     rawName: am.CodeName,
                     visualizationHints: am.VisualizationHints,
-                    code: (am as CodeAttributeModel).Code
+                    code: (am).Code
                 });
         }
         else {
-            throw new Exception()
+            throw new Exception();
         }
     }
 
diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
index 995bf4e0b..120b034f2 100644
--- a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
 
 export class AlphabeticVisualBinRange extends VisualBinRange {
     public DataBinRange: AlphabeticBinRange;
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
index f5872aa4c..776e643cd 100644
--- a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea';
+import { VisualBinRange } from './VisualBinRange';
 
 export class DateTimeVisualBinRange extends VisualBinRange {
     public DataBinRange: DateTimeBinRange;
diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
index 407ff3ea6..42509d797 100644
--- a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { NominalBinRange, BinLabel } from '../../model/idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { NominalBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
 
 export class NominalVisualBinRange extends VisualBinRange {
     public DataBinRange: NominalBinRange;
diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
index 80886416b..c579c8e5f 100644
--- a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
@@ -1,4 +1,4 @@
-import { QuantitativeBinRange } from '../idea/idea'
+import { QuantitativeBinRange } from '../idea/idea';
 import { VisualBinRange } from './VisualBinRange';
 import { format } from "d3-format";
 
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
index a0766e494..449a22e91 100644
--- a/src/client/northstar/model/binRanges/VisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -1,4 +1,4 @@
-import { BinLabel } from '../../model/idea/idea'
+import { BinLabel } from '../../model/idea/idea';
 
 export abstract class VisualBinRange {
 
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
index 63ee61909..9671e55f8 100644
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -16,18 +16,18 @@ export class VisualBinRangeHelper {
 
     public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange {
         if (dataBinRange instanceof NominalBinRange) {
-            return new NominalVisualBinRange(dataBinRange as NominalBinRange);
+            return new NominalVisualBinRange(dataBinRange);
         }
         else if (dataBinRange instanceof QuantitativeBinRange) {
-            return new QuantitativeVisualBinRange(dataBinRange as QuantitativeBinRange);
+            return new QuantitativeVisualBinRange(dataBinRange);
         }
         else if (dataBinRange instanceof AlphabeticBinRange) {
-            return new AlphabeticVisualBinRange(dataBinRange as AlphabeticBinRange);
+            return new AlphabeticVisualBinRange(dataBinRange);
         }
         else if (dataBinRange instanceof DateTimeBinRange) {
-            return new DateTimeVisualBinRange(dataBinRange as DateTimeBinRange);
+            return new DateTimeVisualBinRange(dataBinRange);
         }
-        throw new Exception()
+        throw new Exception();
     }
 
     public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
@@ -51,13 +51,13 @@ export class VisualBinRangeHelper {
                         }
                     }
                 }
-            };
+            }
 
             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 as QuantitativeVisualBinRange).DataBinRange.maxValue!),
+                    Math.max(0, (visualBinRange).DataBinRange.maxValue!),
                     SETTINGS_X_BINS, false);
             }
             else if (chartType === ChartType.SinglePoint) {
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index a14337763..c6d5f0a15 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -1,4 +1,4 @@
-import { FilterModel } from '../core/filter/FilterModel'
+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';
@@ -62,8 +62,9 @@ export abstract class BaseOperation {
             }
 
             let operationParameters = this.CreateOperationParameters();
-            if (this.Result)
-                this.Result.progress = 0; // bcz: used to set Result to undefined, but that causes the display to blink
+            if (this.Result) {
+                this.Result.progress = 0;
+            } // bcz: used to set Result to undefined, but that causes the display to blink
             this.Error = "";
             let salt = Math.random().toString();
             this.RequestSalt = salt;
@@ -97,7 +98,7 @@ export abstract class BaseOperation {
                 pollPromise.Start(async () => {
                     let result = await Gateway.Instance.GetResult(resultParameters.toJSON());
                     if (result instanceof ErrorResult) {
-                        throw new Error((result as ErrorResult).message);
+                        throw new Error((result).message);
                     }
                     if (this.RequestSalt === pollPromise.RequestSalt) {
                         if (result && (!this.Result || this.Result.progress !== result.progress)) {
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index a3ddc1c98..760106023 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -62,7 +62,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @computed
     public get FilterString(): string {
         let filterModels: FilterModel[] = [];
-        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
+        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
     }
 
     @computed
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
index a52ca6d96..12b8d8e77 100644
--- a/src/client/northstar/utils/ArrayUtil.ts
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -50,7 +50,7 @@ export class ArrayUtil {
         if (filtered.length > 0) {
             return filtered[0];
         }
-        throw new Exception()
+        throw new Exception();
     }
 
     public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined {
diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts
index 7c2b7fc9d..df14d4da0 100644
--- a/src/client/northstar/utils/Extensions.ts
+++ b/src/client/northstar/utils/Extensions.ts
@@ -6,7 +6,7 @@ interface 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;
@@ -14,7 +14,7 @@ String.prototype.Truncate = function (length: number, replacement: string): Stri
         target = target.slice(0, Math.max(0, length - replacement.length)) + replacement;
     }
     return target;
-}
+};
 
 interface Math {
     log10(val: number): number;
@@ -22,7 +22,7 @@ interface Math {
 
 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
index 6d8acea20..d5220c479 100644
--- a/src/client/northstar/utils/GeometryUtil.ts
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -8,15 +8,19 @@ export class GeometryUtil {
         let minY: number = Number.MAX_VALUE;
         let maxX: number = Number.MIN_VALUE;
         let maxY: number = Number.MIN_VALUE;
-        for (var i = 0; i < points.length; i++) {
-            if (points[i].x < minX)
-                minX = points[i].x;
-            if (points[i].y < minY)
-                minY = points[i].y;
-            if (points[i].x > maxX)
-                maxX = points[i].x;
-            if (points[i].y > maxY)
-                maxY = points[i].y;
+        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);
     }
@@ -35,7 +39,7 @@ export class GeometryUtil {
                 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));
     }
 
@@ -54,9 +58,9 @@ export class GeometryUtil {
             return [];
         }
 
-        for (let v = 0; v < points.length; v++) {
-            x = points[v].x;
-            y = points[v].y;
+        for (const point of points) {
+            x = point.x;
+            y = point.y;
             sum_x += x;
             sum_y += y;
             sum_xx += x * x;
@@ -68,8 +72,8 @@ export class GeometryUtil {
         let b = (sum_y / count) - (m * sum_x) / count;
         let result: PIXIPoint[] = new Array<PIXIPoint>();
 
-        for (let v = 0; v < points.length; v++) {
-            x = points[v].x;
+        for (const point of points) {
+            x = point.x;
             y = x * m + b;
             result.push(new PIXIPoint(x, y));
         }
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
index 7aa255096..4b44f40c3 100644
--- a/src/client/northstar/utils/MathUtil.ts
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -16,11 +16,11 @@ export class PIXIRectangle {
     public x: number;
     public y: number;
     public width: number;
-    public height: number
-    public get left() { return this.x }
+    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 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;
@@ -127,14 +127,18 @@ export class MathUtil {
     }
 
     public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
-        if (p.x < rect.left - this.EPSILON)
+        if (p.x < rect.left - this.EPSILON) {
             return false;
-        if (p.x > rect.right + this.EPSILON)
+        }
+        if (p.x > rect.right + this.EPSILON) {
             return false;
-        if (p.y < rect.top - this.EPSILON)
+        }
+        if (p.y < rect.top - this.EPSILON) {
             return false;
-        if (p.y > rect.bottom + this.EPSILON)
+        }
+        if (p.y > rect.bottom + this.EPSILON) {
             return false;
+        }
 
         return true;
     }
@@ -145,23 +149,27 @@ export class MathUtil {
         var r3 = new PIXIPoint(rect.right, rect.bottom);
         var r4 = new PIXIPoint(rect.left, rect.bottom);
         var ret = new Array<PIXIPoint>();
-        var dist = this.Dist(lineFrom, lineTo)
+        var dist = this.Dist(lineFrom, lineTo);
         var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
         if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            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)
+            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)
+            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)
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
             ret.push(inter);
+        }
         return ret;
     }
 
@@ -178,7 +186,7 @@ export class MathUtil {
     }
 
     public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
-        return p1.x * p2.x + p1.y * p2.y
+        return p1.x * p2.x + p1.y * p2.y;
     }
 
     public static Normalize(p1: PIXIPoint) {
@@ -193,7 +201,7 @@ export class MathUtil {
     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)
+        return (a * a + b * b);
     }
 
     public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index b5c4a16ab..a52890ed9 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -32,8 +32,8 @@ export class SizeConverter {
         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 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));
@@ -62,19 +62,20 @@ export class SizeConverter {
 
     public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) {
         var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
-        if (value && value.hasResult)
+        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))]
+        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))]
+        return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))];
     }
 
     public DataToScreenX(x: number): number {
@@ -85,8 +86,9 @@ export class SizeConverter {
         return flip ? (this.RenderDimension) - retY : retY;
     }
     public DataToScreenCoord(v: number, axis: number) {
-        if (axis === 0)
+        if (axis === 0) {
             return this.DataToScreenX(v);
+        }
         return this.DataToScreenY(v);
     }
     public DataToScreenRange(minVal: number, maxVal: number, axis: number) {
@@ -94,6 +96,6 @@ export class SizeConverter {
         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 }
+        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
index ac8617e3b..e9b6e0297 100644
--- a/src/client/northstar/utils/StyleContants.ts
+++ b/src/client/northstar/utils/StyleContants.ts
@@ -11,7 +11,7 @@ export class StyleConstants {
 
     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 BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd];
     static GAP: number = 3;
 
     static BACKGROUND_COLOR: number = 0xF3F3F3;
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
index c96b4cbd9..d071dec62 100644
--- a/src/client/northstar/utils/Utils.ts
+++ b/src/client/northstar/utils/Utils.ts
@@ -1,7 +1,7 @@
-import { IBaseBrushable } from '../core/brusher/IBaseBrushable'
-import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer'
-import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider'
-import { AggregateFunction } from '../model/idea/idea'
+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 {
 
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb489edb6..f38b8ca75 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
-import React = require('react')
+import React = require('react');
 import { observer } from 'mobx-react';
 import { observable, action, computed } from 'mobx';
-import { Document } from "../../fields/Document"
+import { Document } from "../../fields/Document";
 import { DocumentView } from '../views/nodes/DocumentView';
 import { KeyStore } from '../../fields/KeyStore';
 import { FieldWaiting } from '../../fields/Field';
@@ -50,7 +50,7 @@ export class DocumentManager {
             if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
                 toReturn = view;
             }
-        })
+        });
 
         return (toReturn);
     }
@@ -71,7 +71,7 @@ export class DocumentManager {
                     toReturn.push(view);
                 }
             }
-        })
+        });
 
         return (toReturn);
     }
@@ -86,8 +86,8 @@ export class DocumentManager {
                         let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
                         if (linkToDoc && linkToDoc !== FieldWaiting) {
                             DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                                pairs.push({ a: dv, b: docView1, l: link })
-                            })
+                                pairs.push({ a: dv, b: docView1, l: link });
+                            });
                         }
                     }
                     return pairs;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f95b2c29d..4849ae9f7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -103,7 +103,7 @@ export namespace DragManager {
         element.addEventListener("dashOnDrop", handler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
-            delete element.dataset.canDrop
+            delete element.dataset.canDrop;
         };
     }
 
@@ -216,8 +216,9 @@ export namespace DragManager {
         const moveHandler = (e: PointerEvent) => {
             e.stopPropagation();
             e.preventDefault();
-            if (dragData instanceof DocumentDragData)
+            if (dragData instanceof DocumentDragData) {
                 dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+            }
             if (e.shiftKey) {
                 abortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2a3c1da6e..92944bec0 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,13 +1,13 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"
-import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'
-import { redo, undo } from 'prosemirror-history'
-import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list'
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands';
+import { redo, undo } from 'prosemirror-history';
+import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
 import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
 import { EditorView, } from "prosemirror-view";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
-  preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]
+  preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
 
 
 // :: Object
@@ -24,7 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     content: "inline*",
     group: "block",
     parseDOM: [{ tag: "p" }],
-    toDOM() { return pDOM }
+    toDOM() { return pDOM; }
   },
 
   // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
@@ -33,14 +33,14 @@ export const nodes: { [index: string]: NodeSpec } = {
     group: "block",
     defining: true,
     parseDOM: [{ tag: "blockquote" }],
-    toDOM() { return blockquoteDOM }
+    toDOM() { return blockquoteDOM; }
   },
 
   // :: NodeSpec A horizontal rule (`<hr>`).
   horizontal_rule: {
     group: "block",
     parseDOM: [{ tag: "hr" }],
-    toDOM() { return hrDOM }
+    toDOM() { return hrDOM; }
   },
 
   // :: NodeSpec A heading textblock, with a `level` attribute that
@@ -57,7 +57,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     { tag: "h4", attrs: { level: 4 } },
     { tag: "h5", attrs: { level: 5 } },
     { tag: "h6", attrs: { level: 6 } }],
-    toDOM(node: any) { return ["h" + node.attrs.level, 0] }
+    toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
   },
 
   // :: NodeSpec A code listing. Disallows marks or non-text inline
@@ -70,7 +70,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     code: true,
     defining: true,
     parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-    toDOM() { return preDOM }
+    toDOM() { return preDOM; }
   },
 
   // :: NodeSpec The text node.
@@ -96,10 +96,10 @@ export const nodes: { [index: string]: NodeSpec } = {
           src: dom.getAttribute("src"),
           title: dom.getAttribute("title"),
           alt: dom.getAttribute("alt")
-        }
+        };
       }
     }],
-    toDOM(node: any) { return ["img", node.attrs] }
+    toDOM(node: any) { return ["img", node.attrs]; }
   },
 
   // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
@@ -108,7 +108,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     group: "inline",
     selectable: false,
     parseDOM: [{ tag: "br" }],
-    toDOM() { return brDOM }
+    toDOM() { return brDOM; }
   },
 
   ordered_list: {
@@ -136,7 +136,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     ...listItem,
     content: 'paragraph block*'
   }
-}
+};
 
 const emDOM: DOMOutputSpecArray = ["em", 0];
 const strongDOM: DOMOutputSpecArray = ["strong", 0];
@@ -156,17 +156,17 @@ export const marks: { [index: string]: MarkSpec } = {
     inclusive: false,
     parseDOM: [{
       tag: "a[href]", getAttrs(dom: any) {
-        return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }
+        return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
       }
     }],
-    toDOM(node: any) { return ["a", node.attrs, 0] }
+    toDOM(node: any) { return ["a", node.attrs, 0]; }
   },
 
   // :: 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 }
+    toDOM() { return emDOM; }
   },
 
   // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
@@ -175,7 +175,7 @@ export const marks: { [index: string]: MarkSpec } = {
     parseDOM: [{ tag: "strong" },
     { tag: "b" },
     { style: "font-weight" }],
-    toDOM() { return strongDOM }
+    toDOM() { return strongDOM; }
   },
 
   underline: {
@@ -221,9 +221,9 @@ export const marks: { [index: string]: MarkSpec } = {
   // :: MarkSpec Code font mark. Represented as a `<code>` element.
   code: {
     parseDOM: [{ tag: "code" }],
-    toDOM() { return codeDOM }
+    toDOM() { return codeDOM; }
   }
-}
+};
 
 // :: Schema
 // This schema rougly corresponds to the document schema used by
@@ -233,4 +233,4 @@ export const marks: { [index: string]: MarkSpec } = {
 //
 // 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 })
\ No newline at end of file
+export const schema = new Schema({ nodes, marks });
\ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index bf9b8266f..468484928 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -14,7 +14,7 @@ import { ListField } from "../../fields/ListField";
 // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
 
 // @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
 import { Documents } from "../documents/Documents";
 import { Key } from "../../fields/Key";
 
@@ -50,7 +50,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
 
     let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
     let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
-    let params: any[] = [KeyStore, Documents, ...fieldTypes]
+    let params: any[] = [KeyStore, Documents, ...fieldTypes];
     let compiledFunction = new Function(...paramNames, `return ${script}`);
     let run = (args: { [name: string]: any } = {}): ScriptResult => {
         let argsArray: any[] = [];
@@ -67,7 +67,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
         } catch (error) {
             return { success: false, error };
         }
-    }
+    };
     return { compiled: true, run };
 }
 
@@ -90,14 +90,14 @@ class ScriptingCompilerHost {
     }
     // getDefaultLibFileName(options: ts.CompilerOptions): string {
     getDefaultLibFileName(options: any): string {
-        return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+        return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
     }
     writeFile(fileName: string, content: string) {
         const file = this.files.find(file => file.fileName === fileName);
         if (file) {
             file.content = content;
         } else {
-            this.files.push({ fileName, content })
+            this.files.push({ fileName, content });
         }
     }
     getCurrentDirectory(): string {
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index b6b088170..a209874a3 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 
 export class ScrollBox extends React.Component {
     onWheel = (e: React.WheelEvent) => {
@@ -16,6 +16,6 @@ export class ScrollBox extends React.Component {
             }} onWheel={this.onWheel}>
                 {this.props.children}
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1bb15c86a..5ddaafc72 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -23,7 +23,7 @@ export namespace SelectionManager {
 
         @action
         DeselectAll(): void {
-            manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false))
+            manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
             manager.SelectedDocuments = [];
         }
     }
@@ -46,7 +46,7 @@ export namespace SelectionManager {
             }
         }
 
-        manager.DeselectAll()
+        manager.DeselectAll();
         if (found) manager.SelectDoc(found, false);
         Main.Instance.SetTextDoc(undefined, undefined);
     }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 913472aa0..bd5753093 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -5,12 +5,12 @@ import { keymap } from "prosemirror-keymap";
 import { EditorState, Transaction, NodeSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { schema } from "./RichTextSchema";
-import { Schema, NodeType } from "prosemirror-model"
-import React = require("react")
+import { Schema, NodeType } from "prosemirror-model";
+import React = require("react");
 import "./TooltipTextMenu.scss";
 const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { wrapInList, bulletList } from 'prosemirror-schema-list'
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { wrapInList, bulletList } from 'prosemirror-schema-list';
 import { faListUl } from '@fortawesome/free-solid-svg-icons';
 
 
@@ -39,7 +39,7 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
             //this doesn't work currently - look into notion of active block
             { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
-        ]
+        ];
         items.forEach(({ dom }) => this.tooltip.appendChild(dom));
 
         //pointer down handler to activate button effects
@@ -52,8 +52,8 @@ export class TooltipTextMenu {
                     //uncomment this if we want the bullet button to disappear if current selection is bulleted
                     // dom.style.display = active ? "" : "none"
                 }
-            })
-        })
+            });
+        });
 
         this.update(view, undefined);
     }
@@ -99,32 +99,32 @@ export class TooltipTextMenu {
         return {
             command: setBlockType(schema.nodes.heading, { level }),
             dom: this.icon("H" + level, "heading")
-        }
+        };
     }
 
     //updates the tooltip menu when the selection changes
     update(view: EditorView, lastState: EditorState | undefined) {
-        let state = view.state
+        let state = view.state;
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) return
+            lastState.selection.eq(state.selection)) return;
 
         // Hide the tooltip if the selection is empty
         if (state.selection.empty) {
-            this.tooltip.style.display = "none"
-            return
+            this.tooltip.style.display = "none";
+            return;
         }
 
         // Otherwise, reposition it and update its content
-        this.tooltip.style.display = ""
-        let { from, to } = state.selection
-        let start = view.coordsAtPos(from), end = view.coordsAtPos(to)
+        this.tooltip.style.display = "";
+        let { from, to } = state.selection;
+        let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
         // The box in which the tooltip is positioned, to use as base
-        let box = this.tooltip.offsetParent!.getBoundingClientRect()
+        let box = this.tooltip.offsetParent!.getBoundingClientRect();
         // Find a center-ish x position from the selection endpoints (when
         // crossing lines, end may be more to the left)
-        let left = Math.max((start.left + end.left) / 2, start.left + 3)
-        this.tooltip.style.left = (left - box.left) + "px"
+        let left = Math.max((start.left + end.left) / 2, start.left + 3);
+        this.tooltip.style.left = (left - box.left) + "px";
         let width = Math.abs(start.left - end.left) / 2;
         let mid = Math.min(start.left, end.left) + width;
 
@@ -133,5 +133,5 @@ export class TooltipTextMenu {
         this.tooltip.style.bottom = (box.bottom - start.top) + "px";
     }
 
-    destroy() { this.tooltip.remove() }
+    destroy() { this.tooltip.remove(); }
 }
\ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index ed4282874..e9170ec36 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -62,19 +62,19 @@ export class Transform {
         return this;
     }
 
-    translated = (x: number, y: number): Transform => this.copy().translate(x, y)
+    translated = (x: number, y: number): Transform => this.copy().translate(x, y);
 
-    preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y)
+    preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y);
 
-    scaled = (scale: number): Transform => this.copy().scale(scale)
+    scaled = (scale: number): Transform => this.copy().scale(scale);
 
-    scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y)
+    scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y);
 
-    preScaled = (scale: number): Transform => this.copy().preScale(scale)
+    preScaled = (scale: number): Transform => this.copy().preScale(scale);
 
-    transformed = (transform: Transform): Transform => this.copy().transform(transform)
+    transformed = (transform: Transform): Transform => this.copy().transform(transform);
 
-    preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform)
+    preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
 
     transformPoint = (x: number, y: number): [number, number] => {
         x *= this._scale;
@@ -84,7 +84,7 @@ export class Transform {
         return [x, y];
     }
 
-    transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale]
+    transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
 
     transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
         [x, y] = this.transformPoint(x, y);
@@ -92,8 +92,8 @@ export class Transform {
         return { x, y, width, height };
     }
 
-    inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
+    inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale);
 
-    copy = () => new Transform(this._translateX, this._translateY, this._scale)
+    copy = () => new Transform(this._translateX, this._translateY, this._scale);
 
 }
\ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 1b251da25..532ba78eb 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -36,5 +36,5 @@ export class TypedEvent<T> {
     this.listenersOncer = [];
   }
 
-  pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e))
+  pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
 }
\ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index b4ea4acae..27aed4bac 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,5 @@
 import { observable, action } from "mobx";
-import 'source-map-support/register'
+import 'source-map-support/register';
 import { Without } from "../../Utils";
 import { string } from "prop-types";
 
@@ -31,9 +31,9 @@ function propertyDecorator(target: any, key: string | symbol) {
                         batch.end();
                     }
                 }
-            })
+            });
         }
-    })
+    });
 }
 
 export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
@@ -43,11 +43,11 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
         return function () {
             let batch = UndoManager.StartBatch("");
             try {
-                return target.apply(undefined, arguments)
+                return target.apply(undefined, arguments);
             } finally {
                 batch.end();
             }
-        }
+        };
     }
     if (!descriptor) {
         propertyDecorator(target, key);
@@ -58,11 +58,11 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
     descriptor.value = function (...args: any[]) {
         let batch = UndoManager.StartBatch(getBatchName(target, key));
         try {
-            return oldFunction.apply(this, args)
+            return oldFunction.apply(this, args);
         } finally {
             batch.end();
         }
-    }
+    };
 
     return descriptor;
 }
@@ -117,8 +117,8 @@ export namespace UndoManager {
             EndBatch(cancel);
         }
 
-        end = () => { this.dispose(false); }
-        cancel = () => { this.dispose(true); }
+        end = () => { this.dispose(false); };
+        cancel = () => { this.dispose(true); };
     }
 
     export function StartBatch(batchName: string): Batch {
@@ -138,7 +138,7 @@ export namespace UndoManager {
             redoStack.length = 0;
             currentBatch = undefined;
         }
-    })
+    });
 
     export function RunInBatch(fn: () => void, batchName: string) {
         let batch = StartBatch(batchName);
@@ -166,7 +166,7 @@ export namespace UndoManager {
         undoing = false;
 
         redoStack.push(commands);
-    })
+    });
 
     export const Redo = action(() => {
         if (redoStack.length === 0) {
@@ -185,6 +185,6 @@ export namespace UndoManager {
         undoing = false;
 
         undoStack.push(commands);
-    })
+    });
 
 }
\ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index cfa8ea7b7..615a928ad 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -2,11 +2,11 @@ import React = require("react");
 import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
 import { observable, action } from "mobx";
 import { observer } from "mobx-react";
-import "./ContextMenu.scss"
+import "./ContextMenu.scss";
 
 @observer
 export class ContextMenu extends React.Component {
-    static Instance: ContextMenu
+    static Instance: ContextMenu;
 
     @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }];
     @observable private _pageX: number = 0;
@@ -22,15 +22,15 @@ export class ContextMenu extends React.Component {
     constructor(props: Readonly<{}>) {
         super(props);
 
-        this.ref = React.createRef()
+        this.ref = React.createRef();
 
         ContextMenu.Instance = this;
     }
 
     @action
     clearItems() {
-        this._items = []
-        this._display = "none"
+        this._items = [];
+        this._display = "none";
     }
 
     @action
@@ -56,7 +56,7 @@ export class ContextMenu extends React.Component {
 
         this._searchString = "";
 
-        this._display = "flex"
+        this._display = "flex";
     }
 
     intersects = (x: number, y: number): boolean => {
@@ -86,7 +86,7 @@ export class ContextMenu extends React.Component {
                 {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
                     map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
             </div>
-        )
+        );
     }
 
     @action
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 4801c1555..70813f0dd 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -11,7 +11,7 @@ export interface SubmenuProps {
 }
 
 export interface ContextMenuItemProps {
-    type: ContextMenuProps | SubmenuProps
+    type: ContextMenuProps | SubmenuProps;
 }
 
 export class ContextMenuItem extends React.Component<ContextMenuProps> {
@@ -20,6 +20,6 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
             <div className="contextMenu-item" onClick={this.props.event}>
                 <div className="contextMenu-description">{this.props.description}</div>
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 285d145a2..b29fb6a2b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -21,8 +21,8 @@ export const Flyout = higflyout.default;
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
-    static Instance: DocumentDecorations
-    private _resizer = ""
+    static Instance: DocumentDecorations;
+    private _resizer = "";
     private _isPointerDown = false;
     private keyinput: React.RefObject<HTMLInputElement>;
     private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
@@ -40,8 +40,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
 
     constructor(props: Readonly<{}>) {
-        super(props)
-        DocumentDecorations.Instance = this
+        super(props);
+        DocumentDecorations.Instance = this;
         this.handleChange = this.handleChange.bind(this);
         this.keyinput = React.createRef();
     }
@@ -49,7 +49,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action
     handleChange = (event: any) => {
         this._title = event.target.value;
-    };
+    }
 
     @action
     enterPressed = (e: any) => {
@@ -59,14 +59,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             var text = e.target.value;
             if (text[0] === '#') {
                 let command = text.slice(1, text.length);
-                this._fieldKey = new Key(command)
+                this._fieldKey = new Key(command);
                 // if (command === "Title" || command === "title") {
                 //     this._fieldKey = KeyStore.Title;
                 // }
                 // else if (command === "Width" || command === "width") {
                 //     this._fieldKey = KeyStore.Width;
                 // }
-                this._title = "changed"
+                this._title = "changed";
                 // TODO: Change field with switch statement
             }
             else {
@@ -89,7 +89,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
-            }
+            };
         }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
     }
 
@@ -104,7 +104,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.addEventListener("pointermove", this.onBackgroundMove);
         document.removeEventListener("pointerup", this.onBackgroundUp);
         document.addEventListener("pointerup", this.onBackgroundUp);
-        this._lastDrag = [e.clientX, e.clientY]
+        this._lastDrag = [e.clientX, e.clientY];
         e.stopPropagation();
         e.preventDefault();
     }
@@ -127,7 +127,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 dragComplete: action(() => this._dragging = false),
             },
             hideSource: true
-        })
+        });
         e.stopPropagation();
     }
 
@@ -199,53 +199,53 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     onLinkerButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
-        document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved);
         document.addEventListener("pointermove", this.onLinkerButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkerButtonUp)
+        document.removeEventListener("pointerup", this.onLinkerButtonUp);
         document.addEventListener("pointerup", this.onLinkerButtonUp);
     }
     onLinkerButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkerButtonMoved)
-        document.removeEventListener("pointerup", this.onLinkerButtonUp)
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+        document.removeEventListener("pointerup", this.onLinkerButtonUp);
         e.stopPropagation();
     }
 
     onLinkerButtonMoved = (e: PointerEvent): void => {
         if (this._linkerButton.current !== null) {
-            document.removeEventListener("pointermove", this.onLinkerButtonMoved)
-            document.removeEventListener("pointerup", this.onLinkerButtonUp)
+            document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+            document.removeEventListener("pointerup", this.onLinkerButtonUp);
             let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
             DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
                 handlers: {
                     dragComplete: action(() => { }),
                 },
                 hideSource: false
-            })
+            });
         }
         e.stopPropagation();
     }
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
-        document.removeEventListener("pointermove", this.onLinkButtonMoved)
+        document.removeEventListener("pointermove", this.onLinkButtonMoved);
         document.addEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp)
+        document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
     }
 
     onLinkButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkButtonMoved)
-        document.removeEventListener("pointerup", this.onLinkButtonUp)
+        document.removeEventListener("pointermove", this.onLinkButtonMoved);
+        document.removeEventListener("pointerup", this.onLinkButtonUp);
         e.stopPropagation();
     }
 
     onLinkButtonMoved = async (e: PointerEvent) => {
         if (this._linkButton.current !== null) {
-            document.removeEventListener("pointermove", this.onLinkButtonMoved)
+            document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
             let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-            let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+            let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
             let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
                 srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
                     (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
@@ -265,7 +265,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         dragComplete: action(() => { }),
                     },
                     hideSource: false
-                })
+                });
             }
         }
         e.stopPropagation();
@@ -285,38 +285,38 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             case "":
                 break;
             case "documentDecorations-topLeftResizer":
-                dX = -1
-                dY = -1
-                dW = -(e.movementX)
-                dH = -(e.movementY)
+                dX = -1;
+                dY = -1;
+                dW = -(e.movementX);
+                dH = -(e.movementY);
                 break;
             case "documentDecorations-topRightResizer":
-                dW = e.movementX
-                dY = -1
-                dH = -(e.movementY)
+                dW = e.movementX;
+                dY = -1;
+                dH = -(e.movementY);
                 break;
             case "documentDecorations-topResizer":
-                dY = -1
-                dH = -(e.movementY)
+                dY = -1;
+                dH = -(e.movementY);
                 break;
             case "documentDecorations-bottomLeftResizer":
-                dX = -1
-                dW = -(e.movementX)
-                dH = e.movementY
+                dX = -1;
+                dW = -(e.movementX);
+                dH = e.movementY;
                 break;
             case "documentDecorations-bottomRightResizer":
-                dW = e.movementX
-                dH = e.movementY
+                dW = e.movementX;
+                dH = e.movementY;
                 break;
             case "documentDecorations-bottomResizer":
-                dH = e.movementY
+                dH = e.movementY;
                 break;
             case "documentDecorations-leftResizer":
-                dX = -1
-                dW = -(e.movementX)
+                dX = -1;
+                dW = -(e.movementX);
                 break;
             case "documentDecorations-rightResizer":
-                dW = e.movementX
+                dW = e.movementX;
                 break;
         }
 
@@ -338,14 +338,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 var nativeWidth = doc.GetNumber(KeyStore.NativeWidth, 0);
                 var nativeHeight = doc.GetNumber(KeyStore.NativeHeight, 0);
                 if (nativeWidth > 0 && nativeHeight > 0) {
-                    if (Math.abs(dW) > Math.abs(dH))
+                    if (Math.abs(dW) > Math.abs(dH)) {
                         actualdH = nativeHeight / nativeWidth * actualdW;
+                    }
                     else actualdW = nativeWidth / nativeHeight * actualdH;
                 }
                 doc.SetNumber(KeyStore.Width, actualdW);
                 doc.SetNumber(KeyStore.Height, actualdH);
             }
-        })
+        });
     }
 
     onPointerUp = (e: PointerEvent): void => {
@@ -362,10 +363,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (this._title === "changed" && this._documents.length > 0) {
             let field = this._documents[0].props.Document.Get(this._fieldKey);
             if (field instanceof TextField) {
-                return (field as TextField).GetValue();
+                return (field).GetValue();
             }
             else if (field instanceof NumberField) {
-                return (field as NumberField).GetValue().toString();
+                return (field).GetValue().toString();
             }
         }
         return this._title;
@@ -388,7 +389,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             return (null);
         }
         if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
-            console.log("DocumentDecorations: Bounds Error")
+            console.log("DocumentDecorations: Bounds Error");
             return (null);
         }
 
@@ -414,7 +415,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: this._dragging ? "none" : "all",
                 zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
-            }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation() }} >
+            }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             <div id="documentDecorations-container" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
@@ -440,6 +441,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
             </div >
         </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 982aacdea..2f17c6c51 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,7 +1,7 @@
-import React = require('react')
+import React = require('react');
 import { observer } from 'mobx-react';
 import { observable, action, trace } from 'mobx';
-import "./EditableView.scss"
+import "./EditableView.scss";
 
 export interface EditableProps {
     /**
@@ -22,7 +22,7 @@ export interface EditableProps {
      * The contents to render when not editing
      */
     contents: any;
-    height: number
+    height: number;
     display?: string;
 }
 
@@ -55,14 +55,14 @@ export class EditableView extends React.Component<EditableProps> {
     render() {
         if (this.editing) {
             return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
-                style={{ display: this.props.display }}></input>
+                style={{ display: this.props.display }}></input>;
         } else {
             return (
                 <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
                     onClick={action(() => this.editing = true)} >
                     {this.props.contents}
                 </div>
-            )
+            );
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 4ecc44119..47ee8eb85 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -82,7 +82,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
             });
             this.inkData = data;
         }
-    };
+    }
 
     @action
     onPointerUp = (e: PointerEvent): void => {
@@ -109,7 +109,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        e.stopPropagation()
+        e.stopPropagation();
         e.preventDefault();
         if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
             let data = this.inkData;  // add points to new line as it is being drawn
@@ -120,7 +120,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
             }
             this.inkData = data;
         }
-    };
+    }
 
     relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
         let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
@@ -137,14 +137,15 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
+        let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
-            if (strokeData.page === -1 || strokeData.page === curPage)
+            if (strokeData.page === -1 || strokeData.page === curPage) {
                 paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
                     offsetX={this.maxCanvasDim - this.inkMidX}
                     offsetY={this.maxCanvasDim - this.inkMidY}
                     color={strokeData.color} width={strokeData.width}
-                    tool={strokeData.tool} deleteCallback={this.removeLine} />)
+                    tool={strokeData.tool} deleteCallback={this.removeLine} />);
+            }
             return paths;
         }, [] as JSX.Element[]);
         return [<svg className={`inkingCanvas-paths-markers`} key="Markers"
@@ -165,6 +166,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 {this.props.children()}
                 {this.drawnPaths}
             </div >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 64ee66ec7..9a68f0671 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,10 +1,10 @@
 import { observable, action, computed } from "mobx";
 
-import { CirclePicker, ColorResult } from 'react-color'
+import { CirclePicker, ColorResult } from 'react-color';
 import React = require("react");
 import { InkTool } from "../../fields/InkField";
 import { observer } from "mobx-react";
-import "./InkingControl.scss"
+import "./InkingControl.scss";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
@@ -25,7 +25,7 @@ export class InkingControl extends React.Component {
 
     constructor(props: Readonly<{}>) {
         super(props);
-        InkingControl.Instance = this
+        InkingControl.Instance = this;
     }
 
     @action
@@ -66,9 +66,9 @@ export class InkingControl extends React.Component {
 
     selected = (tool: InkTool) => {
         if (this._selectedTool === tool) {
-            return { color: "#61aaa3" }
+            return { color: "#61aaa3" };
         }
-        return {}
+        return {};
     }
 
     @action
@@ -111,6 +111,6 @@ export class InkingControl extends React.Component {
                         onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
                 </li>
             </ul >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 12b15a3f0..0f05da22c 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -30,7 +30,7 @@ export class InkingStroke extends React.Component<StrokeProps> {
     }
 
     parseData = (line: Array<{ x: number, y: number }>): string => {
-        return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ")
+        return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
     }
 
     createStyle() {
@@ -41,7 +41,7 @@ export class InkingStroke extends React.Component<StrokeProps> {
                     fill: "none",
                     stroke: this._strokeColor,
                     strokeWidth: this._strokeWidth + "px",
-                }
+                };
         }
     }
 
@@ -53,6 +53,6 @@ export class InkingStroke extends React.Component<StrokeProps> {
         return (
             <path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
                 onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 6376fd694..fd2e23c91 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -16,7 +16,7 @@ import { WorkspacesMenu } from '../../server/authentication/controllers/Workspac
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { MessageStore } from '../../server/Message';
 import { Utils, returnTrue, emptyFunction } from '../../Utils';
-import * as rp from 'request-promise'
+import * as rp from 'request-promise';
 import { RouteStore } from '../../server/RouteStore';
 import { ServerUtils } from '../../server/ServerUtil';
 import { Documents } from '../documents/Documents';
@@ -74,7 +74,7 @@ export class Main extends React.Component {
             if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
                 CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
             }
-        };
+        }
 
         CurrentUserUtils.loadCurrentUser();
 
@@ -119,8 +119,8 @@ export class Main extends React.Component {
 
     initEventListeners = () => {
         // window.addEventListener("pointermove", (e) => this.reportLocation(e))
-        window.addEventListener("drop", (e) => e.preventDefault(), false) // drop event handler
-        window.addEventListener("dragover", (e) => e.preventDefault(), false) // drag event handler
+        window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
+        window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", action(function (e: PointerEvent) {
             if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
@@ -139,15 +139,15 @@ export class Main extends React.Component {
                 } else {
                     this.createNewWorkspace();
                 }
-            })
+            });
         } else {
             Server.GetField(CurrentUserUtils.MainDocId).then(field => {
                 if (field instanceof Document) {
-                    this.openWorkspace(field)
+                    this.openWorkspace(field);
                 } else {
                     this.createNewWorkspace(CurrentUserUtils.MainDocId);
                 }
-            })
+            });
         }
     }
 
@@ -163,7 +163,7 @@ export class Main extends React.Component {
                 // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
                 setTimeout(() => {
                     this.openWorkspace(mainDoc);
-                    let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" })
+                    let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" });
                     mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
                 }, 0);
             }
@@ -182,7 +182,7 @@ export class Main extends React.Component {
                         if (f && f.Data.length > 0) {
                             CollectionDockingView.Instance.AddRightSplit(col);
                         }
-                    })
+                    });
                 }
             }, 100);
         });
@@ -191,7 +191,7 @@ export class Main extends React.Component {
     @observable
     workspacesShown: boolean = false;
 
-    areWorkspacesShown = () => this.workspacesShown
+    areWorkspacesShown = () => this.workspacesShown;
     @action
     toggleWorkspaces = () => {
         this.workspacesShown = !this.workspacesShown;
@@ -199,7 +199,7 @@ export class Main extends React.Component {
 
     pwidthFunc = () => this.pwidth;
     pheightFunc = () => this.pheight;
-    focusDocument = (doc: Document) => { }
+    focusDocument = (doc: Document) => { };
     noScaling = () => 1;
 
     @observable _textDoc?: Document = undefined;
@@ -208,8 +208,9 @@ export class Main extends React.Component {
     SetTextDoc(textDoc?: Document, div?: HTMLDivElement) {
         this._textDoc = undefined;
         this._textDoc = textDoc;
-        if (div)
+        if (div) {
             this._textRect = div.getBoundingClientRect();
+        }
     }
 
     @computed
@@ -221,7 +222,7 @@ export class Main extends React.Component {
             let h: number = this._textRect.height;
             return <div className="mainDiv-textInput" style={{ transform: `translate(${x}px, ${y}px)`, width: `${w}px`, height: `${h}px` }} >
                 <FormattedTextBox fieldKey={KeyStore.Archives} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={Transform.Identity} focus={(doc) => { }} />
-            </ div>
+            </ div>;
         }
         else return (null);
     }
@@ -241,19 +242,19 @@ export class Main extends React.Component {
                 focus={this.focusDocument}
                 parentActive={returnTrue}
                 onActiveChanged={emptyFunction}
-                ContainingCollectionView={undefined} />
+                ContainingCollectionView={undefined} />;
     }
 
     /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
     @computed
     get nodesMenu() {
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
-        let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
+        let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf";
         let weburl = "https://cs.brown.edu/courses/cs166/";
         let audiourl = "http://techslides.com/demos/samples/sample.mp3";
         let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
 
-        let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
+        let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }));
         let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
         let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
@@ -261,7 +262,7 @@ export class Main extends React.Component {
         let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
         let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
         let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
-        let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
+        let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Document][] = [
             [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
@@ -273,7 +274,7 @@ export class Main extends React.Component {
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
             [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
             [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
-        ]
+        ];
 
         return < div id="add-nodes-menu" >
             <input type="checkbox" id="add-menu-toggle" />
@@ -289,7 +290,7 @@ export class Main extends React.Component {
                         </div></li>)}
                 </ul>
             </div>
-        </div >
+        </div >;
     }
 
     /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
@@ -298,7 +299,7 @@ export class Main extends React.Component {
         let workspacesRef = React.createRef<HTMLDivElement>();
         let logoutRef = React.createRef<HTMLDivElement>();
 
-        let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
+        let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}));
         return [
             <button className="clear-db-button" key="clear-db" onClick={clearDatabase}>Clear Database</button>,
             <div id="toolbar" key="toolbar">
@@ -310,7 +311,7 @@ export class Main extends React.Component {
                 <button onClick={this.toggleWorkspaces}>Workspaces</button></div>,
             <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
                 <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), () => { })}>Log Out</button></div>
-        ]
+        ];
     }
 
     render() {
@@ -318,7 +319,7 @@ export class Main extends React.Component {
         let workspaces = this.userDocument.GetT<ListField<Document>>(KeyStore.Workspaces, ListField);
         if (workspaces && workspaces !== FieldWaiting) {
             workspaceMenu = <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={workspaces.Data}
-                isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} />
+                isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} />;
         }
         return (
             <>
@@ -359,7 +360,7 @@ export class Main extends React.Component {
                             schemaDocuments.push(field);
                         } else {
                             var atmod = new ColumnAttributeModel(attr);
-                            let histoOp = new HistogramOperation(schema!.displayName!,
+                            let histoOp = new HistogramOperation(schema.displayName!,
                                 new AttributeTransformationModel(atmod, AggregateFunction.None),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count));
@@ -368,7 +369,7 @@ export class Main extends React.Component {
                     }));
                 });
                 return schemaDoc;
-            })
+            });
         }
     }
     async initializeNorthstar(): Promise<void> {
@@ -386,7 +387,7 @@ export class Main extends React.Component {
 }
 
 (async () => {
-    await Documents.initProtos()
-    await CurrentUserUtils.loadCurrentUser()
+    await Documents.initProtos();
+    await CurrentUserUtils.loadCurrentUser();
     ReactDOM.render(<Main />, document.getElementById('root'));
-})()
+})();
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 7a5ab6e3c..32462a4c1 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -66,17 +66,20 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
         let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
         for (const doc of data) {
-            if (this.createsCycle(doc, containerDocument))
+            if (this.createsCycle(doc, containerDocument)) {
                 return true;
+            }
         }
         let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
         for (const annot of annots) {
-            if (this.createsCycle(annot, containerDocument))
+            if (this.createsCycle(annot, containerDocument)) {
                 return true;
+            }
         }
         for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) {
-            if (containerProto.Id === documentToAdd.Id)
+            if (containerProto.Id === documentToAdd.Id) {
                 return true;
+            }
         }
         return false;
     }
@@ -91,20 +94,23 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         }
         if (props.Document.Get(props.fieldKey) instanceof Field) {
             //TODO This won't create the field if it doesn't already exist
-            const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
+            const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
             if (!this.createsCycle(doc, props.Document)) {
-                if (!value.some(v => v.Id === doc.Id) || allowDuplicates)
+                if (!value.some(v => v.Id === doc.Id) || allowDuplicates) {
                     value.push(doc);
+                }
             }
-            else
+            else {
                 return false;
+            }
         } else {
             let proto = props.Document.GetPrototype();
             if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
                 props.Document.SetOnPrototype(props.fieldKey, new ListField([doc]));
             }
-            else
+            else {
                 return false;
+            }
         }
         return true;
     }
@@ -113,7 +119,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     removeDocument(doc: Document): boolean {
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
-        const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
+        const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
         let index = -1;
         for (let i = 0; i < value.length; i++) {
             if (value[i].Id === doc.Id) {
@@ -125,16 +131,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             if (annotationOn === props.Document) {
                 doc.Set(KeyStore.AnnotationOn, undefined, true);
             }
-        })
+        });
 
         if (index !== -1) {
-            value.splice(index, 1)
+            value.splice(index, 1);
 
             // SelectionManager.DeselectAll()
-            ContextMenu.Instance.clearItems()
+            ContextMenu.Instance.clearItems();
             return true;
         }
-        return false
+        return false;
     }
 
     @action.bound
@@ -155,12 +161,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             moveDocument: this.moveDocument,
             active: this.active,
             onActiveChanged: this.onActiveChanged,
-        }
+        };
         return (
             <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {this.props.children(this.collectionViewType, props)}
             </div>
-        )
+        );
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c3757a377..ea6d3a247 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -32,7 +32,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 documentId: document.Id,
                 //collectionDockingView: CollectionDockingView.Instance
             }
-        }
+        };
     }
 
     private _goldenLayout: any = null;
@@ -58,7 +58,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
-        }
+        };
         var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
         docconfig.callDownwards('_$init');
@@ -86,7 +86,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
-        }
+        };
 
         var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
 
@@ -124,8 +124,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 this._goldenLayout = new GoldenLayout(JSON.parse(config));
             }
             else {
-                if (config === JSON.stringify(this._goldenLayout.toConfig()))
+                if (config === JSON.stringify(this._goldenLayout.toConfig())) {
                     return;
+                }
                 try {
                     this._goldenLayout.unbind('itemDropped', this.itemDropped);
                     this._goldenLayout.unbind('tabCreated', this.tabCreated);
@@ -199,14 +200,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             let docid = (e.target as any).DashDocId;
             let tab = (e.target as any).parentElement as HTMLElement;
             Server.GetField(docid, action((f: Opt<Field>) => {
-                if (f instanceof Document)
-                    DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f as Document]), e.pageX, e.pageY,
+                if (f instanceof Document) {
+                    DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), e.pageX, e.pageY,
                         {
                             handlers: {
                                 dragComplete: action(() => { }),
                             },
                             hideSource: false
-                        })
+                        });
+                }
             }));
         }
         if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
@@ -220,7 +222,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     stateChanged = () => {
         var json = JSON.stringify(this._goldenLayout.toConfig());
-        this.props.Document.SetText(KeyStore.Data, json)
+        this.props.Document.SetText(KeyStore.Data, json);
     }
 
     itemDropped = () => {
@@ -236,7 +238,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             if (tfield !== undefined) {
                                 tab.titleElement[0].textContent = f.Title;
                             }
-                        })
+                        });
                     }
                 }));
                 tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
@@ -281,7 +283,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 }
 
 interface DockedFrameProps {
-    documentId: FieldId,
+    documentId: FieldId;
     //collectionDockingView: CollectionDockingView
 }
 @observer
@@ -297,18 +299,19 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
     }
 
-    private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth)
-    private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight)
-    private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth)
+    private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
+    private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
+    private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth);
 
     ScreenToLocalTransform = () => {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
-        return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling())
+        return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling());
     }
 
     render() {
-        if (!this._document)
+        if (!this._document) {
             return (null);
+        }
         var content =
             <div className="collectionDockingView-content" ref={this._mainCont}>
                 <DocumentView key={this._document.Id} Document={this._document}
@@ -324,10 +327,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     onActiveChanged={emptyFunction}
                     focus={(doc: Document) => { }}
                     ContainingCollectionView={undefined} />
-            </div>
+            </div>;
 
         return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
             {({ measureRef }) => <div ref={measureRef}>  {content} </div>}
-        </Measure>
+        </Measure>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 32f50afc0..97bac745c 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -2,7 +2,7 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
-import "./CollectionPDFView.scss"
+import "./CollectionPDFView.scss";
 import React = require("react");
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
@@ -44,7 +44,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
                 <CollectionFreeFormView {...props} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
             </>
-        )
+        );
     }
 
     render() {
@@ -52,6 +52,6 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
             <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
                 {this.subView}
             </CollectionBaseView>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9b780f29b..52933818f 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -41,7 +41,7 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
             if (field instanceof Key) {
                 this.key = field;
             }
-        }))
+        }));
     }
 
     render() {
@@ -49,7 +49,7 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
             return (<div key={this.key.Id}>
                 <input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />
                 {this.key.Name}
-            </div>)
+            </div>);
         }
         return (null);
     }
@@ -85,10 +85,10 @@ export class CollectionSchemaView extends CollectionSubView {
             focus: emptyFunction,
             active: returnFalse,
             onActiveChanged: emptyFunction,
-        }
+        };
         let contents = (
             <FieldView {...props} />
-        )
+        );
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument);
         let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => {
@@ -106,7 +106,7 @@ export class CollectionSchemaView extends CollectionSubView {
                 }
             }
             return false;
-        }
+        };
         return (
             <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
                 <EditableView
@@ -138,11 +138,11 @@ export class CollectionSchemaView extends CollectionSubView {
                             if (val) {
                                 val.Data.forEach(doc => applyToDoc(doc, run));
                             }
-                        })
+                        });
                     }}>
                 </EditableView>
             </div>
-        )
+        );
     }
 
     private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
@@ -156,7 +156,7 @@ export class CollectionSchemaView extends CollectionSubView {
                 that._selectedIndex = rowInfo.index;
 
                 if (handleOriginal) {
-                    handleOriginal()
+                    handleOriginal();
                 }
             }),
             style: {
@@ -182,7 +182,7 @@ export class CollectionSchemaView extends CollectionSubView {
                     this.columns.splice(index, 1);
                 }
 
-            })
+            });
     }
 
     //toggles preview side-panel of schema
@@ -197,7 +197,7 @@ export class CollectionSchemaView extends CollectionSubView {
     @computed
     get findAllDocumentKeys(): { [id: string]: boolean } {
         const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
-        let keys: { [id: string]: boolean } = {}
+        let keys: { [id: string]: boolean } = {};
         if (this._optionsActivated > -1) {
             // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
             //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
@@ -206,7 +206,7 @@ export class CollectionSchemaView extends CollectionSubView {
             //  is displayed (unlikely) it won't show up until something else changes.
             untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
         }
-        this.columns.forEach(key => keys[key.Id] = true)
+        this.columns.forEach(key => keys[key.Id] = true);
         return keys;
     }
 
@@ -248,10 +248,10 @@ export class CollectionSchemaView extends CollectionSubView {
     getContentScaling = (): number => this._contentScaling;
     getPanelWidth = (): number => this._panelWidth;
     getPanelHeight = (): number => this._panelHeight;
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling)
-    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling)
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
+    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
 
-    focusDocument = (doc: Document) => { }
+    focusDocument = (doc: Document) => { };
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (this.props.isSelected()) {
@@ -273,8 +273,9 @@ export class CollectionSchemaView extends CollectionSubView {
         this.newKeyName = e.currentTarget.value;
     }
     onWheel = (e: React.WheelEvent): void => {
-        if (this.props.active())
+        if (this.props.active()) {
             e.stopPropagation();
+        }
     }
 
     @observable _optionsActivated: number = 0;
@@ -322,9 +323,9 @@ export class CollectionSchemaView extends CollectionSubView {
                     </div>
                 }
             </Measure>
-        )
+        );
         let dividerDragger = this.splitPercentage === 0 ? (null) :
-            <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
+            <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
 
         //options button and menu
         let optionsMenu = !this.props.active() ? (null) : (<Flyout
@@ -379,6 +380,6 @@ export class CollectionSchemaView extends CollectionSubView {
                     {optionsMenu}
                 </div>
             </div >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 59b89e119..bd385c27e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -14,7 +14,7 @@ import { NumberField } from "../../../fields/NumberField";
 import { ServerUtils } from "../../../server/ServerUtil";
 import { Server } from "../../Server";
 import { FieldViewProps } from "../nodes/FieldView";
-import * as rp from 'request-promise'
+import * as rp from 'request-promise';
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
@@ -58,8 +58,8 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                         let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
                         cursors.push(entry);
                     }
-                }))
-            })
+                }));
+            });
         }
     }
 
@@ -77,17 +77,17 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
             } else if (de.data.moveDocument) {
                 const move = de.data.moveDocument;
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false)
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false);
             } else {
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false)
+                added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
             }
             e.stopPropagation();
             return added;
         }
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
-            if (sourceDoc) runInAction(() => {
-                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+            if (sourceDoc) { runInAction(() => {
+                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
                 if (srcTarg && srcTarg !== FieldWaiting) {
                     let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]);
                     linkDocs.map(linkDoc => {
@@ -97,9 +97,10 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                             de.data.droppedDocuments.push(dropdoc);
                             this.props.addDocument(dropdoc, false);
                         }
-                    })
+                    });
                 }
-            })
+            });
+            }
             return true;
         }
         return false;
@@ -133,7 +134,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                         alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
                         this.props.addDocument(alias, false);
                     }
-                })
+                });
                 return undefined;
             }
             ctor = Documents.WebDocument;
@@ -151,8 +152,8 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
         if (text && text.startsWith("<div")) {
             return;
         }
-        e.stopPropagation()
-        e.preventDefault()
+        e.stopPropagation();
+        e.preventDefault();
 
         if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
             console.log("not good");
@@ -173,11 +174,11 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 let prom = new Promise<string>(res =>
                     e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => {
                         str = s;
-                        return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s))
+                        return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s));
                     })).then(res => {
                         let type = res.headers["content-type"];
                         if (type) {
-                            let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+                            let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 });
                             if (doc) {
                                 this.props.addDocument(doc, false);
                             }
@@ -186,13 +187,13 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 promises.push(prom);
                 // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false)
             }
-            let type = item.type
+            let type = item.type;
             if (item.kind === "file") {
                 let file = item.getAsFile();
-                let formData = new FormData()
+                let formData = new FormData();
 
                 if (file) {
-                    formData.append('file', file)
+                    formData.append('file', file);
                 }
 
                 let prom = fetch(upload, {
@@ -201,15 +202,15 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
                 }).then(async (res: Response) => {
                     const json = await res.json();
                     json.map((file: any) => {
-                        let path = window.location.origin + file
+                        let path = window.location.origin + file;
                         runInAction(() => {
-                            let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 })
+                            let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 });
 
                             let docs = this.props.Document.GetT(KeyStore.Data, ListField);
                             if (docs !== FieldWaiting) {
                                 if (!docs) {
                                     docs = new ListField<Document>();
-                                    this.props.Document.Set(KeyStore.Data, docs)
+                                    this.props.Document.Set(KeyStore.Data, docs);
                                 }
                                 if (doc) {
                                     docs.Data.push(doc);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2bdf0baa6..659cff9fe 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,7 +12,7 @@ import { EditableView } from "../EditableView";
 import "./CollectionTreeView.scss";
 import { CollectionView } from "./CollectionView";
 import { CollectionSubView } from "./CollectionSubView";
-import React = require("react")
+import React = require("react");
 import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
 import { props } from 'bluebird';
 
@@ -58,7 +58,7 @@ class TreeView extends React.Component<TreeViewProps> {
             return true;
         }
         //TODO This should check if it was removed
-        this.remove(document)
+        this.remove(document);
         return addDoc(document);
     }
 
@@ -69,7 +69,7 @@ class TreeView extends React.Component<TreeViewProps> {
             case BulletType.Collapsed: bullet = "caret-right"; break;
             case BulletType.Collapsible: bullet = "caret-down"; break;
         }
-        return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>
+        return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
     }
 
     /**
@@ -93,7 +93,7 @@ class TreeView extends React.Component<TreeViewProps> {
             <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
                 {editableView(this.props.document.Title)}
                 <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
-            </div >)
+            </div >);
     }
 
     render() {
@@ -105,7 +105,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 bulletType = BulletType.Collapsible;
                 childElements = <ul>
                     {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
-                </ul >
+                </ul >;
             }
             else bulletType = BulletType.Collapsed;
         }
@@ -115,7 +115,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 {this.renderTitle()}
                 {childElements ? childElements : (null)}
             </li>
-        </div>
+        </div>;
     }
 }
 
@@ -136,7 +136,7 @@ export class CollectionTreeView extends CollectionSubView {
         let childrenElement = !children || children === FieldWaiting ? (null) :
             (children.Data.map(value =>
                 <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
-            )
+            );
 
         return (
             <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 1acc76c85..b02983a2e 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -4,7 +4,7 @@ import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
 import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
 import React = require("react");
-import "./CollectionVideoView.scss"
+import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 
@@ -111,7 +111,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
                 <CollectionFreeFormView {...props} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
             </>
-        )
+        );
     }
 
     render() {
@@ -119,6 +119,6 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
         return (
             <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}>
                 {this.subView}
-            </CollectionBaseView>)
+            </CollectionBaseView>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b31dcc888..8abd0a02d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react'
+import * as React from 'react';
 import { FieldViewProps, FieldView } from '../nodes/FieldView';
 import { CollectionBaseView, CollectionViewType, CollectionRenderProps } from './CollectionBaseView';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
@@ -13,26 +13,26 @@ import { undoBatch } from '../../util/UndoManager';
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) }
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr); }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (type) {
-            case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />)
-            case CollectionViewType.Docking: return (<CollectionDockingView {...props} />)
-            case CollectionViewType.Tree: return (<CollectionTreeView {...props} />)
+            case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />);
+            case CollectionViewType.Docking: return (<CollectionDockingView {...props} />);
+            case CollectionViewType.Tree: return (<CollectionTreeView {...props} />);
             case CollectionViewType.Freeform:
             default:
-                return (<CollectionFreeFormView {...props} />)
+                return (<CollectionFreeFormView {...props} />);
         }
         return (null);
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) })
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) })
-            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) })
+            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) });
+            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) });
+            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) });
         }
     }
 
@@ -41,6 +41,6 @@ export class CollectionView extends React.Component<FieldViewProps> {
             <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}>
                 {this.SubView}
             </CollectionBaseView>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 3dfd74ec8..081b3eb6c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -32,6 +32,6 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 style={{ strokeWidth: `${l.length * 5}` }}
                 x1={`${x1}`} y1={`${y1}`}
                 x2={`${x2}`} y2={`${y2}`} />
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index c288e7abf..cf058090d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -17,7 +17,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     HackToAvoidReactionFiringUnnecessarily?: Document = undefined;
     componentDidMount() {
-        this.HackToAvoidReactionFiringUnnecessarily = this.props.Document
+        this.HackToAvoidReactionFiringUnnecessarily = this.props.Document;
         reaction(() =>
             DocumentManager.Instance.getAllDocumentViews(this.HackToAvoidReactionFiringUnnecessarily!).
                 map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)),
@@ -31,13 +31,14 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                         let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
                         let x2 = dstDoc.GetNumber(KeyStore.X, 0);
                         let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
-                        if (x1w < 0 || x2w < 0 || i === j)
+                        if (x1w < 0 || x2w < 0 || i === j) {
                             continue;
+                        }
                         let dstTarg = dstDoc;
                         let srcTarg = srcDoc;
                         let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
                             let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : [];
-                            return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false)
+                            return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false);
                         });
                         let brushAction = (field: ListField<Document>) => {
                             let found = findBrush(field);
@@ -64,13 +65,13 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
                     }
                 }
-            })
+            });
     }
     documentAnchors(view: DocumentView) {
         let equalViews = [view];
         let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
         if (containerDoc && containerDoc instanceof Document) {
-            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!)
+            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!);
         }
         return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document);
     }
@@ -86,15 +87,16 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                 if (!drawnPairs.reduce((found, drawnPair) => {
                     let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
                     if (match) {
-                        if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false))
+                        if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) {
                             drawnPair.l.push(connection.l);
+                        }
                     }
                     return match || found;
                 }, false)) {
                     drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] });
                 }
-            })
-            return drawnPairs
+            });
+            return drawnPairs;
         }, [] as { a: Document, b: Document, l: Document[] }[]);
         return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 17656e4ae..fc832264d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -57,7 +57,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                 let id = entry.Data[0][0];
                 let email = entry.Data[0][1];
                 let point = entry.Data[1];
-                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22");
                 return (
                     <div
                         key={id}
@@ -69,7 +69,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                         }}
                     >
                         <canvas
-                            ref={(el) => { if (el) this.crosshairs = el }}
+                            ref={(el) => { if (el) this.crosshairs = el; }}
                             width={20}
                             height={20}
                             style={{
@@ -93,7 +93,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                     </div>
                 );
             }
-        })
+        });
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1c3899b24..e694bc7a7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -49,7 +49,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             if (dv) {
                 SelectionManager.SelectDoc(dv, true);
             }
-        })
+        });
     }
 
     public getActiveDocuments = () => {
@@ -70,8 +70,8 @@ export class CollectionFreeFormView extends CollectionSubView {
 
     private outerElement?: HTMLDivElement;
 
-    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
-    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
+    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0); }
+    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0); }
     @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
     @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
     @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
@@ -109,7 +109,7 @@ export class CollectionFreeFormView extends CollectionSubView {
                             d.SetNumber(KeyStore.Height, 300);
                         }
                         this.bringToFront(d);
-                    })
+                    });
                 }
             }
             return true;
@@ -133,8 +133,9 @@ export class CollectionFreeFormView extends CollectionSubView {
             document.addEventListener("pointerup", this.onPointerUp);
             this._lastX = this.DownX = e.pageX;
             this._lastY = this.DownY = e.pageY;
-            if (this.props.isSelected())
+            if (this.props.isSelected()) {
                 e.stopPropagation();
+            }
         }
     }
 
@@ -182,12 +183,13 @@ export class CollectionFreeFormView extends CollectionSubView {
             let transform = this.getTransform();
 
             let deltaScale = (1 - (e.deltaY / coefficient));
-            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
+            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) {
                 deltaScale = 1 / this.zoomScaling;
+            }
             let [x, y] = transform.transformPoint(e.clientX, e.clientY);
 
-            let localTransform = this.getLocalTransform()
-            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+            let localTransform = this.getLocalTransform();
+            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y);
             // console.log(localTransform)
 
             this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
@@ -209,7 +211,7 @@ export class CollectionFreeFormView extends CollectionSubView {
     onDrop = (e: React.DragEvent): void => {
         var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
         super.onDrop(e, { x: pt[0], y: pt[1] });
-    };
+    }
 
     onDragOver = (): void => {
     }
@@ -228,7 +230,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             }
             return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
         }).map((doc, index) => {
-            doc.SetNumber(KeyStore.ZIndex, index + 1)
+            doc.SetNumber(KeyStore.ZIndex, index + 1);
         });
     }
 
@@ -268,7 +270,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             focus: this.focusDocument,
             parentActive: this.props.active,
             onActiveChanged: this.props.active,
-        }
+        };
     }
 
     @computed
@@ -276,10 +278,11 @@ export class CollectionFreeFormView extends CollectionSubView {
         var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
         return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
             var page = doc.GetNumber(KeyStore.Page, -1);
-            if (page === curPage || page === -1)
+            if (page === curPage || page === -1) {
                 prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+            }
             return prev;
-        }, [] as JSX.Element[])
+        }, [] as JSX.Element[]);
     }
 
     @computed
@@ -295,8 +298,8 @@ export class CollectionFreeFormView extends CollectionSubView {
                 layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
     }
 
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
-    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
+    getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH);
     getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
     noScaling = () => 1;
     childViews = () => this.views;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 704db1d4a..1e6faafb3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -37,7 +37,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @action
     cleanupInteractions = (all: boolean = false) => {
         if (all) {
-            document.removeEventListener("pointermove", this.onPointerMove, true)
+            document.removeEventListener("pointermove", this.onPointerMove, true);
             document.removeEventListener("pointerup", this.onPointerUp, true);
         } else {
             this._used = true;
@@ -52,7 +52,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this._downX = this._lastX = e.pageX;
             this._downY = this._lastY = e.pageY;
             this._used = false;
-            document.addEventListener("pointermove", this.onPointerMove, true)
+            document.addEventListener("pointermove", this.onPointerMove, true);
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
         }
@@ -94,7 +94,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let top = this._downY < this._lastY ? this._downY : this._lastY;
         let topLeft = this.props.getTransform().transformPoint(left, top);
         let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
     }
 
     @action
@@ -180,9 +180,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             var y = doc.GetNumber(KeyStore.Y, 0);
             var w = doc.GetNumber(KeyStore.Width, 0);
             var h = doc.GetNumber(KeyStore.Height, 0);
-            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
-                selection.push(doc)
-        })
+            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+                selection.push(doc);
+            }
+        });
         return selection;
     }
 
@@ -190,7 +191,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     get marqueeDiv() {
         let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
         let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />
+        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />;
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
index 599461f85..8eabb020a 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -90,7 +90,7 @@ export class PreviewCursor extends React.Component<PreviewCursorProps>  {
                 {this.props.children}
                 <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} />
             </div>
-        )
+        );
     }
 }
 
@@ -109,8 +109,9 @@ export class PreviewCursorPrompt extends React.Component<PromptProps> {
 
     render() {
         let p = this.props.getPoint();
-        if (this.props.getVisible() && this._promptRef.current)
+        if (this.props.getVisible() && this._promptRef.current) {
             this._promptRef.current.focus();
+        }
         return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef}
             style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}>
             I
diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
index e4f17940c..3e4ed6bf1 100644
--- a/src/client/views/nodes/Annotation.tsx
+++ b/src/client/views/nodes/Annotation.tsx
@@ -1,8 +1,8 @@
 import "./ImageBox.scss";
-import React = require("react")
-import { observer } from "mobx-react"
+import React = require("react");
+import { observer } from "mobx-react";
 import { observable, action } from 'mobx';
-import 'react-pdf/dist/Page/AnnotationLayer.css'
+import 'react-pdf/dist/Page/AnnotationLayer.css';
 
 interface IProps {
     Span: HTMLSpanElement;
@@ -29,13 +29,13 @@ export class Annotation extends React.Component<IProps> {
      */
     onColorChange = (e: React.PointerEvent) => {
         if (e.currentTarget.innerHTML === "r") {
-            this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)";
         } else if (e.currentTarget.innerHTML === "b") {
-            this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)";
         } else if (e.currentTarget.innerHTML === "y") {
-            this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)";
         } else if (e.currentTarget.innerHTML === "g") {
-            this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"
+            this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)";
         }
 
     }
@@ -54,11 +54,11 @@ export class Annotation extends React.Component<IProps> {
                     this.props.Highlights.splice(index, 1);
                 }
             }
-        })
+        });
 
         //removing from CurrAnno and Annotation array 
         this.props.Annotations.splice(index, 1);
-        this.props.CurrAnno.pop()
+        this.props.CurrAnno.pop();
 
         //removing span from div
         if (this.props.Span.parentElement) {
@@ -70,11 +70,11 @@ export class Annotation extends React.Component<IProps> {
                             if (item === e) {
                                 item.remove();
                             }
-                        })
+                        });
                         e.remove();
                     }
                 }
-            })
+            });
         }
 
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1bd934c25..1493ff25b 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,18 +1,18 @@
-import React = require("react")
+import React = require("react");
 import { FieldViewProps, FieldView } from './FieldView';
 import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
 import { ContextMenu } from "../../views/ContextMenu";
 import { observable, action } from 'mobx';
 import { KeyStore } from '../../../fields/KeyStore';
 import { AudioField } from "../../../fields/AudioField";
-import "./AudioBox.scss"
+import "./AudioBox.scss";
 import { NumberField } from "../../../fields/NumberField";
 
 @observer
 export class AudioBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString() { return FieldView.LayoutString(AudioBox) }
+    public static LayoutString() { return FieldView.LayoutString(AudioBox); }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -28,7 +28,7 @@ export class AudioBox extends React.Component<FieldViewProps> {
 
 
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey)
+        let field = this.props.Document.Get(this.props.fieldKey);
         let path = field === FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
             field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
 
@@ -39,6 +39,6 @@ export class AudioBox extends React.Component<FieldViewProps> {
                     Not supported.
                 </audio>
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index dcf82ccd4..77f41105f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -35,24 +35,24 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
     @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
 
     set width(w: number) {
-        this.props.Document.SetData(KeyStore.Width, w, NumberField)
+        this.props.Document.SetData(KeyStore.Width, w, NumberField);
         if (this.nativeWidth && this.nativeHeight) {
-            this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
+            this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w);
         }
     }
 
     set height(h: number) {
         this.props.Document.SetData(KeyStore.Height, h, NumberField);
         if (this.nativeWidth && this.nativeHeight) {
-            this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
+            this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h);
         }
     }
 
     set zIndex(h: number) {
-        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
+        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
     }
 
-    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1
+    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
 
     getTransform = (): Transform =>
         this.props.ScreenToLocalTransform()
@@ -66,7 +66,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
-        />
+        />;
     }
     panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
     panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 34cc326aa..5836da396 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -26,7 +26,7 @@ import { FieldViewProps } from "./FieldView";
 import { Without } from "../../../Utils";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
-type BindingProps = Without<FieldViewProps, 'fieldKey'>
+type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
     props: BindingProps;
     [keyName: string]: BindingProps | Field;
@@ -92,7 +92,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
-            onError={(test: any) => { console.log(test) }}
-        />
+            onError={(test: any) => { console.log(test); }}
+        />;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ab9cd2d53..9670ca6b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -100,8 +100,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (e.shiftKey && e.buttons === 2) {
             if (this.props.isTopMost) {
                 this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
-            } else
+            } else {
                 CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+            }
             e.stopPropagation();
         } else {
             if (this.active && !e.isDefaultPrevented()) {
@@ -112,7 +113,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                 document.addEventListener("pointerup", this.onPointerUp);
             }
         }
-    };
+    }
 
     private dropDisposer?: DragManager.DragDropDisposer;
 
@@ -179,7 +180,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         e.stopPropagation();
         e.preventDefault();
-    };
+    }
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
@@ -190,22 +191,22 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         ) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
-    };
+    }
     stopPropogation = (e: React.SyntheticEvent) => {
         e.stopPropagation();
-    };
+    }
 
     deleteClicked = (): void => {
         if (this.props.removeDocument) {
             this.props.removeDocument(this.props.Document);
         }
-    };
+    }
 
     fieldsClicked = (e: React.MouseEvent): void => {
         if (this.props.addDocument) {
             this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
         }
-    };
+    }
     fullScreenClicked = (e: React.MouseEvent): void => {
         CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
         ContextMenu.Instance.clearItems();
@@ -214,7 +215,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             event: this.closeFullScreenClicked
         });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    };
+    }
 
     closeFullScreenClicked = (e: React.MouseEvent): void => {
         CollectionDockingView.Instance.CloseFullScreen();
@@ -224,7 +225,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             event: this.fullScreenClicked
         });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    };
+    }
 
     @action
     public minimize = (): void => {
@@ -234,7 +235,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             BooleanField
         );
         SelectionManager.DeselectAll();
-    };
+    }
 
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -276,7 +277,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             );
             e.stopPropagation();
         }
-    };
+    }
 
     onDrop = (e: React.DragEvent) => {
         if (e.isDefaultPrevented()) {
@@ -290,7 +291,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             e.stopPropagation();
             e.preventDefault();
         }
-    };
+    }
 
     @action
     onContextMenu = (e: React.MouseEvent): void => {
@@ -352,14 +353,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         });
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         SelectionManager.SelectDoc(this, e.ctrlKey);
-    };
+    }
 
     isMinimized = () => {
         let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField);
         if (field && field !== FieldWaiting) {
             return field.Data;
         }
-    };
+    }
 
     @action
     expand = () => {
@@ -368,13 +369,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             false as boolean,
             BooleanField
         );
-    };
+    }
 
-    isSelected = () => SelectionManager.IsSelected(this)
+    isSelected = () => SelectionManager.IsSelected(this);
 
     select = (ctrlPressed: boolean) => {
         SelectionManager.SelectDoc(this, ctrlPressed);
-    };
+    }
 
     render() {
         if (!this.props.Document) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d9422ae9b..07c5b332c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { observer } from "mobx-react";
 import { computed } from "mobx";
 import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
@@ -7,7 +7,7 @@ import { TextField } from "../../../fields/TextField";
 import { NumberField } from "../../../fields/NumberField";
 import { RichTextField } from "../../../fields/RichTextField";
 import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField"
+import { VideoField } from "../../../fields/VideoField";
 import { Key } from "../../../fields/Key";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
@@ -57,22 +57,22 @@ export class FieldView extends React.Component<FieldViewProps> {
     render() {
         const field = this.field;
         if (!field) {
-            return <p>{'<null>'}</p>
+            return <p>{'<null>'}</p>;
         }
         if (field instanceof TextField) {
-            return <p>{field.Data}</p>
+            return <p>{field.Data}</p>;
         }
         else if (field instanceof RichTextField) {
-            return <FormattedTextBox {...this.props} />
+            return <FormattedTextBox {...this.props} />;
         }
         else if (field instanceof ImageField) {
-            return <ImageBox {...this.props} />
+            return <ImageBox {...this.props} />;
         }
         else if (field instanceof VideoField) {
-            return <VideoBox {...this.props} />
+            return <VideoBox {...this.props} />;
         }
         else if (field instanceof AudioField) {
-            return <AudioBox {...this.props} />
+            return <AudioBox {...this.props} />;
         }
         else if (field instanceof Document) {
             return (
@@ -92,25 +92,26 @@ export class FieldView extends React.Component<FieldViewProps> {
                     ContainingCollectionView={undefined}
                     parentActive={this.props.active}
                     onActiveChanged={this.props.onActiveChanged} />
-            )
+            );
         }
         else if (field instanceof ListField) {
             return (<div>
                 {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
-            </div>)
+            </div>);
         }
         // bcz: this belongs here, but it doesn't render well so taking it out for now
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
         else if (field instanceof NumberField) {
-            return <p>{field.Data}</p>
+            return <p>{field.Data}</p>;
         }
         else if (field !== FieldWaiting) {
-            return <p>{JSON.stringify(field.GetValue())}</p>
+            return <p>{JSON.stringify(field.GetValue())}</p>;
         }
-        else
-            return <p> {"Waiting for server..."} </p>
+        else {
+            return <p> {"Waiting for server..."} </p>;
+ }
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7a94be12b..beca6cdc6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -61,7 +61,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
             );
             // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
         }
-    };
+    }
 
     get FieldDoc() { return this.props.fieldKey === KeyStore.Archives ? Main.Instance._textDoc! : this.props.Document; }
     get FieldKey() { return this.props.fieldKey === KeyStore.Archives ? KeyStore.Data : this.props.fieldKey; }
@@ -81,12 +81,13 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         if (this.props.fieldKey === KeyStore.Archives) {
             this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id,
                 () => {
-                    if (this._editorView)
-                        this._editorView!.destroy();
+                    if (this._editorView) {
+                        this._editorView.destroy();
+                    }
 
                     this.setupEditor(config);
                 }
-            )
+            );
         }
 
         this._reactionDisposer = reaction(
@@ -153,7 +154,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
-    };
+    }
     onPointerUp = (e: React.PointerEvent): void => {
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
@@ -162,7 +163,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
             e.preventDefault();
             Main.Instance.SetTextDoc(this.props.Document, this._ref.current!);
         }
-    };
+    }
 
     onFocused = (e: React.FocusEvent): void => {
         if (this.props.fieldKey !== KeyStore.Archives) {
@@ -191,11 +192,11 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
         //     ]
         // })
         // e.stopPropagation()
-    };
+    }
 
     onPointerWheel = (e: React.WheelEvent): void => {
         e.stopPropagation();
-    };
+    }
 
     tooltipMenuPlugin() {
         return new Plugin({
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c5f29f7b0..6b0a3a799 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,13 +9,13 @@ import { KeyStore } from '../../../fields/KeyStore';
 import { ContextMenu } from "../../views/ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
-import React = require("react")
+import React = require("react");
 import { Utils } from '../../../Utils';
 
 @observer
 export class ImageBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString() { return FieldView.LayoutString(ImageBox) }
+    public static LayoutString() { return FieldView.LayoutString(ImageBox); }
     private _ref: React.RefObject<HTMLDivElement>;
     private _imgRef: React.RefObject<HTMLImageElement>;
     private _downX: number = 0;
@@ -39,7 +39,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+        this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
     }
 
     componentDidMount() {
@@ -86,7 +86,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
                 onMoveNextRequest={action(() =>
                     this._photoIndex = (this._photoIndex + 1) % images.length
                 )}
-            />)
+            />);
         }
     }
 
@@ -96,7 +96,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
             let url = field.Data.href;
             ContextMenu.Instance.addItem({
                 description: "Copy path", event: () => {
-                    Utils.CopyText(url)
+                    Utils.CopyText(url);
                 }
             });
         }
@@ -111,6 +111,6 @@ export class ImageBox extends React.Component<FieldViewProps> {
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
                 <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
                 {this.lightbox(path)}
-            </div>)
+            </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 8a7f32e6d..bcac113f0 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -7,7 +7,7 @@ import { KeyStore } from '../../../fields/KeyStore';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
-import React = require("react")
+import React = require("react");
 import { CompileScript, ToField } from "../../util/Scripting";
 import { Key } from '../../../fields/Key';
 import { observable, action } from "mobx";
@@ -15,7 +15,7 @@ import { observable, action } from "mobx";
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
 
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
 
@@ -36,7 +36,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             if (this._keyInput && this._valueInput) {
                 let doc = this.props.Document.GetT(KeyStore.Data, Document);
                 if (!doc || doc === FieldWaiting) {
-                    return
+                    return;
                 }
                 let realDoc = doc;
 
@@ -55,8 +55,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                         realDoc.Set(new Key(this._keyInput), dataField);
                     }
                 }
-                this._keyInput = ""
-                this._valueInput = ""
+                this._keyInput = "";
+                this._valueInput = "";
             }
         }
     }
@@ -73,7 +73,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     createTable = () => {
         let doc = this.props.Document.GetT(KeyStore.Data, Document);
         if (!doc || doc === FieldWaiting) {
-            return <tr><td>Loading...</td></tr>
+            return <tr><td>Loading...</td></tr>;
         }
         let realDoc = doc;
 
@@ -84,13 +84,13 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 if (!(key in ids)) {
                     ids[key] = key;
                 }
-            })
+            });
         }
 
         let rows: JSX.Element[] = [];
         let i = 0;
         for (let key in ids) {
-            rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+            rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />);
         }
         return rows;
     }
@@ -125,6 +125,6 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                     {this.newKeyValue()}
                 </tbody>
             </table>
-        </div>)
+        </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index c6a0a7296..a1050dc6e 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,14 +1,14 @@
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
 import "./KeyValueBox.scss";
 import "./KeyValuePair.scss";
-import React = require("react")
+import React = require("react");
 import { FieldViewProps, FieldView } from './FieldView';
 import { Opt, Field } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
 import { observable, action } from 'mobx';
 import { Document } from '../../../fields/Document';
 import { Key } from '../../../fields/Key';
-import { Server } from "../../Server"
+import { Server } from "../../Server";
 import { EditableView } from "../EditableView";
 import { CompileScript, ToField } from "../../util/Scripting";
 import { Transform } from '../../util/Transform';
@@ -25,7 +25,7 @@ export interface KeyValuePairProps {
 export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
     @observable
-    private key: Opt<Key>
+    private key: Opt<Key>;
 
     constructor(props: KeyValuePairProps) {
         super(props);
@@ -41,7 +41,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
     render() {
         if (!this.key) {
-            return <tr><td>error</td><td></td></tr>
+            return <tr><td>error</td><td></td></tr>;
 
         }
         let props: FieldViewProps = {
@@ -55,7 +55,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             onActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
-        }
+        };
         let contents = (
             <FieldView {...props} />
         );
@@ -101,6 +101,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                         return false;
                     }}></EditableView></td>
             </tr>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 4791d6029..b016a3d48 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,8 +2,8 @@ import { observable, computed, action } from "mobx";
 import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 import { observer } from "mobx-react";
-import './LinkBox.scss'
-import { KeyStore } from '../../../fields/KeyStore'
+import './LinkBox.scss';
+import { KeyStore } from '../../../fields/KeyStore';
 import { props } from "bluebird";
 import { DocumentView } from "./DocumentView";
 import { Document } from "../../../fields/Document";
@@ -30,7 +30,7 @@ interface Props {
     linkName: String;
     pairedDoc: Document;
     type: String;
-    showEditor: () => void
+    showEditor: () => void;
 }
 
 @observer
@@ -49,15 +49,16 @@ export class LinkBox extends React.Component<Props> {
                 } else if (contextDoc instanceof Document) {
                     this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => {
                         contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => {
-                            if (pfield !== cfield)
+                            if (pfield !== cfield) {
                                 contextDoc.SetNumber(KeyStore.CurPage, pfield.Data);
+                            }
                             let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
                             if (contextView) {
                                 contextView.props.focus(contextDoc);
                             } else {
                                 CollectionDockingView.Instance.AddRightSplit(contextDoc);
                             }
-                        })
+                        });
                     });
                 }
             });
@@ -80,7 +81,7 @@ export class LinkBox extends React.Component<Props> {
                     if (field) {
                         field.Data.splice(field.Data.indexOf(this.props.linkDoc));
                     }
-                })
+                });
             }
         });
         this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
@@ -89,7 +90,7 @@ export class LinkBox extends React.Component<Props> {
                     if (field) {
                         field.Data.splice(field.Data.indexOf(this.props.linkDoc));
                     }
-                })
+                });
             }
         });
     }
@@ -117,6 +118,6 @@ export class LinkBox extends React.Component<Props> {
                         <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
                 </div>
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 3f7b4bf2d..bde50fed8 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -2,8 +2,8 @@ import { observable, computed, action } from "mobx";
 import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 import { observer } from "mobx-react";
-import './LinkEditor.scss'
-import { KeyStore } from '../../../fields/KeyStore'
+import './LinkEditor.scss';
+import { KeyStore } from '../../../fields/KeyStore';
 import { props } from "bluebird";
 import { DocumentView } from "./DocumentView";
 import { Document } from "../../../fields/Document";
@@ -43,7 +43,7 @@ export class LinkEditor extends React.Component<Props> {
                 <div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div>
             </div>
 
-        )
+        );
     }
 
     @action
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 6c2d24630..ac09da305 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -13,7 +13,7 @@ import React = require("react");
 
 interface Props {
     docView: DocumentView;
-    changeFlyout: () => void
+    changeFlyout: () => void;
 }
 
 @observer
@@ -25,9 +25,9 @@ export class LinkMenu extends React.Component<Props> {
         return links.map(link => {
             let doc = link.GetT(key, Document);
             if (doc && doc !== FieldWaiting) {
-                return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />
+                return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
             }
-        })
+        });
     }
 
     render() {
@@ -43,11 +43,11 @@ export class LinkMenu extends React.Component<Props> {
                         {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
                     </div>
                 </div>
-            )
+            );
         } else {
             return (
                 <LinkEditor linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
-            )
+            );
         }
 
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 774d9be3e..81ceb37f6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -17,7 +17,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import "./PDFBox.scss";
 import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
-import React = require("react")
+import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
@@ -55,7 +55,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 export class PDFBox extends React.Component<FieldViewProps> {
     public static LayoutString() { return FieldView.LayoutString(PDFBox); }
 
-    private _mainDiv = React.createRef<HTMLDivElement>()
+    private _mainDiv = React.createRef<HTMLDivElement>();
     private _pdf = React.createRef<HTMLCanvasElement>();
 
     @observable private _renderAsSvg = true;
@@ -72,7 +72,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     private _currTool: any; //keeps track of current tool button reference
     private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool 
-    private _drawTool = React.createRef<HTMLButtonElement>()//drawing tool button reference
+    private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference
 
     private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
     private _currColor: string = "black"; //current color that user selected (for ink/pen)
@@ -85,7 +85,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @observable private _perPageInfo: Object[] = []; //stores pageInfo
     @observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno
 
-    @observable private _currAnno: any = []
+    @observable private _currAnno: any = [];
     @observable private _interactive: boolean = false;
     @observable private _loaded: boolean = false;
 
@@ -168,24 +168,24 @@ export class PDFBox extends React.Component<FieldViewProps> {
             let obj: Object = { parentDivs: [], spans: [] };
             //@ts-ignore
             if (range.commonAncestorContainer.className === 'react-pdf__Page__textContent') { //multiline highlighting case
-                obj = this.highlightNodes(range.commonAncestorContainer.childNodes)
+                obj = this.highlightNodes(range.commonAncestorContainer.childNodes);
             } else { //single line highlighting case
-                let parentDiv = range.commonAncestorContainer.parentElement
+                let parentDiv = range.commonAncestorContainer.parentElement;
                 if (parentDiv) {
                     if (parentDiv.className === 'react-pdf__Page__textContent') { //when highlight is overwritten
-                        obj = this.highlightNodes(parentDiv.childNodes)
+                        obj = this.highlightNodes(parentDiv.childNodes);
                     } else {
                         parentDiv.childNodes.forEach((child) => {
                             if (child.nodeName === 'SPAN') {
                                 //@ts-ignore
-                                obj.parentDivs.push(parentDiv)
+                                obj.parentDivs.push(parentDiv);
                                 //@ts-ignore
-                                child.id = "highlighted"
+                                child.id = "highlighted";
                                 //@ts-ignore
-                                obj.spans.push(child)
+                                obj.spans.push(child);
                                 child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
                             }
-                        })
+                        });
                     }
                 }
             }
@@ -196,21 +196,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
     }
 
     highlightNodes = (nodes: NodeListOf<ChildNode>) => {
-        let temp = { parentDivs: [], spans: [] }
+        let temp = { parentDivs: [], spans: [] };
         nodes.forEach((div) => {
             div.childNodes.forEach((child) => {
                 if (child.nodeName === 'SPAN') {
                     //@ts-ignore
-                    temp.parentDivs.push(div)
+                    temp.parentDivs.push(div);
                     //@ts-ignore
-                    child.id = "highlighted"
+                    child.id = "highlighted";
                     //@ts-ignore
-                    temp.spans.push(child)
+                    temp.spans.push(child);
                     child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
                 }
-            })
+            });
 
-        })
+        });
         return temp;
     }
 
@@ -228,8 +228,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
                         index = this._pageInfo.divs.indexOf(obj);
                     }
                 }
-            })
-        })
+            });
+        });
 
         if (this._pageInfo.anno.length >= index + 1) {
             if (this._currAnno.length === 0) {
@@ -239,13 +239,13 @@ export class PDFBox extends React.Component<FieldViewProps> {
             if (this._currAnno.length === 0) { //if there are no current annotation
                 let div = span.offsetParent;
                 //@ts-ignore
-                let divX = div.style.left
+                let divX = div.style.left;
                 //@ts-ignore
-                let divY = div.style.top
+                let divY = div.style.top;
                 //slicing "px" from the end
                 divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span)
                 divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span)
-                let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />
+                let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />;
                 this._pageInfo.anno.push(annotation);
                 this._currAnno.push(annotation);
             }
@@ -263,7 +263,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
                     this.makeEditableAndHighlight(color);
                 }
             } catch (ex) {
-                this.makeEditableAndHighlight(color)
+                this.makeEditableAndHighlight(color);
             }
         }
     }
@@ -305,7 +305,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
             }
 
             if (this._mainDiv.current) {
-                let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />
+                let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
                 this._pageInfo.area.push(sticky);
             }
             this._toolOn = false;
@@ -404,19 +404,19 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
                         if (e instanceof HTMLCanvasElement) {
                             this._pdfCanvas = e;
-                            this._pdfContext = e.getContext("2d")
+                            this._pdfContext = e.getContext("2d");
 
                         }
 
-                    })
+                    });
                 }
-            })
+            });
         }
 
         // bcz: the number of pages should really be set when the document is imported.
         this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
         if (this._perPageInfo.length === 0) { //Makes sure it only runs once
-            this._perPageInfo = [...Array(page._transport.numPages)]
+            this._perPageInfo = [...Array(page._transport.numPages)];
         }
         this._loaded = true;
     }
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
index 4a4d69e90..11719831b 100644
--- a/src/client/views/nodes/Sticky.tsx
+++ b/src/client/views/nodes/Sticky.tsx
@@ -39,7 +39,7 @@ export class Sticky extends React.Component<IProps> {
       document.addEventListener("pointermove", this.drawMove);
       document.addEventListener("pointerup", this.drawUp);
     }
-  };
+  }
 
   //when user drags
   drawMove = (e: PointerEvent): void => {
@@ -49,7 +49,7 @@ export class Sticky extends React.Component<IProps> {
     //connects the point
     this.ctx.lineTo(x, y);
     this.ctx.stroke();
-  };
+  }
 
   /**
    * when user lifts the mouse, the drawing ends
@@ -58,7 +58,7 @@ export class Sticky extends React.Component<IProps> {
     this.ctx.closePath();
     console.log(this.ctx);
     document.removeEventListener("pointermove", this.drawMove);
-  };
+  }
 
   render() {
     return (
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a08b320e8..314af64c9 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
 import { observer } from "mobx-react";
 import { FieldWaiting, Opt } from '../../../fields/Field';
 import { VideoField } from '../../../fields/VideoField';
@@ -13,8 +13,8 @@ import { number } from "prop-types";
 export class VideoBox extends React.Component<FieldViewProps> {
 
     private _reactionDisposer: Opt<IReactionDisposer>;
-    private _videoRef = React.createRef<HTMLVideoElement>()
-    public static LayoutString() { return FieldView.LayoutString(VideoBox) }
+    private _videoRef = React.createRef<HTMLVideoElement>();
+    public static LayoutString() { return FieldView.LayoutString(VideoBox); }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -58,7 +58,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
     render() {
         let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
         if (!field || field === FieldWaiting) {
-            return <div>Loading</div>
+            return <div>Loading</div>;
         }
         let path = field.Data.href;
         trace();
@@ -73,6 +73,6 @@ export class VideoBox extends React.Component<FieldViewProps> {
                     </div>
                 }
             </Measure>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c1d389001..90ce72c41 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,9 +1,9 @@
 import "./WebBox.scss";
-import React = require("react")
+import React = require("react");
 import { WebField } from '../../../fields/WebField';
 import { FieldViewProps, FieldView } from './FieldView';
 import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
 import { computed } from 'mobx';
 import { KeyStore } from '../../../fields/KeyStore';
 
@@ -33,6 +33,6 @@ export class WebBox extends React.Component<FieldViewProps> {
         return (
             <div className="webBox-cont"  >
                 {content}
-            </div>)
+            </div>);
     }
 }
\ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index c8de33f41..11f2b0c4e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,10 +1,10 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import JsxParser from 'react-jsx-parser'
+import JsxParser from 'react-jsx-parser';
 
 class Hello extends React.Component<{ firstName: string, lastName: string }> {
     render() {
-        return <div>Hello {this.props.firstName} {this.props.lastName}</div>
+        return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
     }
 }
 
@@ -16,8 +16,8 @@ class Test extends React.Component {
                 firstName: "First",
                 lastName: "Last"
             }
-        }
-        return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>
+        };
+        return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>;
     }
 }
 
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index f13cccd24..857da1ebb 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -40,22 +40,22 @@ class ListViewer extends React.Component<{ field: ListField<Field> }>{
                 <div>
                     {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
                 </div>
-            )
+            );
         } else {
-            content = <>[...] ({this.props.field.Id})</>
+            content = <>[...] ({this.props.field.Id})</>;
         }
         return (
             <div>
                 <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
                 {content}
             </div >
-        )
+        );
     }
 }
 
 @observer
 class DocumentViewer extends React.Component<{ field: Document }> {
-    private keyMap: ObservableMap<string, Key> = new ObservableMap
+    private keyMap: ObservableMap<string, Key> = new ObservableMap;
 
     private disposer?: Lambda;
 
@@ -67,12 +67,12 @@ class DocumentViewer extends React.Component<{ field: Document }> {
                         if (field && field instanceof Key) {
                             this.keyMap.set(id, field);
                         }
-                    })
+                    });
                 }
             });
-        }
-        this.disposer = this.props.field._proxies.observe(f)
-        f()
+        };
+        this.disposer = this.props.field._proxies.observe(f);
+        f();
     }
 
     componentWillUnmount() {
@@ -89,8 +89,8 @@ class DocumentViewer extends React.Component<{ field: Document }> {
                     <b>({key ? key.Name : kv[0]}): </b>
                     <DebugViewer fieldId={kv[1]}></DebugViewer>
                 </div>
-            )
-        })
+            );
+        });
         return (
             <div>
                 Document ({this.props.field.Id})
@@ -98,7 +98,7 @@ class DocumentViewer extends React.Component<{ field: Document }> {
                     {fields}
                 </div>
             </div>
-        )
+        );
     }
 }
 
@@ -111,15 +111,15 @@ class DebugViewer extends React.Component<{ fieldId: string }> {
     private error?: string;
 
     constructor(props: { fieldId: string }) {
-        super(props)
-        this.update()
+        super(props);
+        this.update();
     }
 
     update() {
         Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
             this.field = field;
             if (!field) {
-                this.error = `Field with id ${this.props.fieldId} not found`
+                this.error = `Field with id ${this.props.fieldId} not found`;
             }
         }));
 
@@ -130,20 +130,20 @@ class DebugViewer extends React.Component<{ fieldId: string }> {
         if (this.field) {
             // content = this.field.ToJson();
             if (this.field instanceof ListField) {
-                content = (<ListViewer field={this.field} />)
+                content = (<ListViewer field={this.field} />);
             } else if (this.field instanceof Document) {
-                content = (<DocumentViewer field={this.field} />)
+                content = (<DocumentViewer field={this.field} />);
             } else if (this.field instanceof BasicField) {
-                content = (<FieldViewer field={this.field} />)
+                content = (<FieldViewer field={this.field} />);
             } else if (this.field instanceof Key) {
-                content = (<KeyViewer field={this.field} />)
+                content = (<KeyViewer field={this.field} />);
             } else {
-                content = (<span>Unrecognized field type</span>)
+                content = (<span>Unrecognized field type</span>);
             }
         } else if (this.error) {
-            content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>
+            content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
         } else {
-            content = <span>Field loading: {this.props.fieldId}</span>
+            content = <span>Field loading: {this.props.fieldId}</span>;
         }
         return content;
     }
@@ -165,8 +165,8 @@ class Viewer extends React.Component {
     @action
     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
         if (e.key === "Enter") {
-            this.ids.push(this.idToAdd)
-            this.idToAdd = ""
+            this.ids.push(this.idToAdd);
+            this.idToAdd = "";
         }
     }
 
@@ -180,7 +180,7 @@ class Viewer extends React.Component {
                     {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
                 </div>
             </>
-        )
+        );
     }
 }
 
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
index 252a5b74e..996d2556d 100644
--- a/src/fields/AudioField.ts
+++ b/src/fields/AudioField.ts
@@ -25,7 +25,7 @@ export class AudioField extends BasicField<URL> {
             type: Types.Audio,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
index 3083a1937..17b1fc4e8 100644
--- a/src/fields/BasicField.ts
+++ b/src/fields/BasicField.ts
@@ -1,4 +1,4 @@
-import { Field, FieldId } from "./Field"
+import { Field, FieldId } from "./Field";
 import { observable, computed, action } from "mobx";
 import { Server } from "../client/Server";
 import { UndoManager } from "../client/util/UndoManager";
@@ -9,7 +9,7 @@ export abstract class BasicField<T> extends Field {
 
         this.data = data;
         if (save) {
-            Server.UpdateField(this)
+            Server.UpdateField(this);
         }
     }
 
@@ -36,7 +36,7 @@ export abstract class BasicField<T> extends Field {
         UndoManager.AddEvent({
             undo: () => this.Data = oldValue,
             redo: () => this.Data = value
-        })
+        });
         Server.UpdateField(this);
     }
 
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 4fa478f32..4584660fb 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -37,21 +37,24 @@ export class Document extends Field {
         }
     }
 
-    public Width = () => this.GetNumber(KeyStore.Width, 0)
+    public Width = () => this.GetNumber(KeyStore.Width, 0);
     public Height = () => this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? (this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0)) * this.GetNumber(KeyStore.Width, 0) : 0);
-    public Scale = () => this.GetNumber(KeyStore.Scale, 1)
+    public Scale = () => this.GetNumber(KeyStore.Scale, 1);
 
     @computed
     public get Title(): string {
         let title = this.Get(KeyStore.Title, true);
-        if (title)
-            if (title !== FieldWaiting && title instanceof TextField)
+        if (title) {
+            if (title !== FieldWaiting && title instanceof TextField) {
                 return title.Data;
+            }
             else return "-waiting-";
+        }
         let parTitle = this.GetT(KeyStore.Title, TextField);
-        if (parTitle)
+        if (parTitle) {
             if (parTitle !== FieldWaiting) return parTitle.Data + ".alias";
             else return "-waiting-.alias";
+        }
         return "-untitled-";
     }
 
@@ -398,11 +401,13 @@ export class Document extends Field {
                         }
                     }
                     else
-                        if (field instanceof Document)  // ... TODO bcz: should we copy documents or reference them
-                            copy.Set(key!, field)
-                        else if (field)
-                            copy.Set(key!, field.Copy())
-                })
+                        if (field instanceof Document) {  // ... TODO bcz: should we copy documents or reference them
+                            copy.Set(key!, field);
+                        }
+                        else if (field) {
+                            copy.Set(key!, field.Copy());
+ }
+                });
             }
         });
         return copy;
diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts
index 9d3c209b4..6c0c1ef82 100644
--- a/src/fields/DocumentReference.ts
+++ b/src/fields/DocumentReference.ts
@@ -52,6 +52,6 @@ export class DocumentReference extends Field {
             type: Types.DocumentReference,
             data: this.document.Id,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index 0d0e56f77..d9db23b9e 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -65,5 +65,5 @@ export abstract class Field {
 
     abstract Copy(): Field;
 
-    abstract ToJson(): { _id: string, type: Types, data: any }
+    abstract ToJson(): { _id: string, type: Types, data: any };
 }
\ No newline at end of file
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
index 7cbdf7e58..65665cf7a 100644
--- a/src/fields/HtmlField.ts
+++ b/src/fields/HtmlField.ts
@@ -20,6 +20,6 @@ export class HtmlField extends BasicField<string> {
             type: Types.Html,
             data: this.Data,
             _id: this.Id,
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
index ef616b2ad..dd843026f 100644
--- a/src/fields/ImageField.ts
+++ b/src/fields/ImageField.ts
@@ -24,6 +24,6 @@ export class ImageField extends BasicField<URL> {
             type: Types.Image,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 2a4ed18e7..ab706ee30 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -36,7 +36,7 @@ export class InkField extends BasicField<StrokeMap> {
             type: Types.Ink,
             data: this.Data,
             _id: this.Id,
-        }
+        };
     }
 
     UpdateFromServer(data: any) {
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
index 00d78d516..c7f806b88 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -1,4 +1,4 @@
-import { Field, FieldId } from "./Field"
+import { Field, FieldId } from "./Field";
 import { Utils } from "../Utils";
 import { observable } from "mobx";
 import { Types } from "../server/Message";
@@ -16,7 +16,7 @@ export class Key extends Field {
 
         this.name = name;
         if (save) {
-            Server.UpdateField(this)
+            Server.UpdateField(this);
         }
     }
 
@@ -45,6 +45,6 @@ export class Key extends Field {
             type: Types.Key,
             data: this.name,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index e24e9c8ec..425408273 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -56,8 +56,9 @@ export namespace KeyStore {
     ];
     export function KeyLookup(keyid: string) {
         for (const key of KeyList) {
-            if (key.Id === keyid)
+            if (key.Id === keyid) {
                 return key;
+            }
         }
         return undefined;
     }
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
index 815a3df73..b6eab5f86 100644
--- a/src/fields/ListField.ts
+++ b/src/fields/ListField.ts
@@ -7,7 +7,7 @@ import { Field, FieldId } from "./Field";
 import { FieldMap } from "../client/SocketStub";
 
 export class ListField<T extends Field> extends BasicField<T[]> {
-    private _proxies: string[] = []
+    private _proxies: string[] = [];
     constructor(data: T[] = [], id?: FieldId, save: boolean = true) {
         super(data, save, id);
         this.updateProxies();
@@ -22,23 +22,24 @@ export class ListField<T extends Field> extends BasicField<T[]> {
     private observeDisposer: Lambda | undefined;
     private observeList(): void {
         if (this.observeDisposer) {
-            this.observeDisposer()
+            this.observeDisposer();
         }
         this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
-            this.updateProxies()
+            this.updateProxies();
             if (change.type === "splice") {
                 UndoManager.AddEvent({
                     undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed),
                     redo: () => this.Data.splice(change.index, change.removedCount, ...change.added)
-                })
+                });
             } else {
                 UndoManager.AddEvent({
                     undo: () => this.Data[change.index] = change.oldValue,
                     redo: () => this.Data[change.index] = change.newValue
-                })
+                });
             }
-            if (!this._processingServerUpdate)
+            if (!this._processingServerUpdate) {
                 Server.UpdateField(this);
+            }
         });
     }
 
@@ -78,19 +79,23 @@ export class ListField<T extends Field> extends BasicField<T[]> {
                 var proxies = this._proxies.map(p => p);
                 var added = this.data.length < this._proxies.length;
                 var deleted = this.data.length > this._proxies.length;
-                for (let i = 0; i < dataids.length && added; i++)
+                for (let i = 0; i < dataids.length && added; i++) {
                     added = proxies.indexOf(dataids[i]) !== -1;
-                for (let i = 0; i < this._proxies.length && deleted; i++)
+                }
+                for (let i = 0; i < this._proxies.length && deleted; i++) {
                     deleted = dataids.indexOf(proxies[i]) !== -1;
+                }
 
                 this._processingServerUpdate = true;
                 for (let i = 0; i < proxies.length && added; i++) {
-                    if (dataids.indexOf(proxies[i]) === -1)
+                    if (dataids.indexOf(proxies[i]) === -1) {
                         this.Data.splice(i, 0, fields[proxies[i]] as T);
+                    }
                 }
                 for (let i = dataids.length - 1; i >= 0 && deleted; i--) {
-                    if (proxies.indexOf(dataids[i]) === -1)
+                    if (proxies.indexOf(dataids[i]) === -1) {
                         this.Data.splice(i, 1);
+                    }
                 }
                 if (!added && !deleted) {// otherwise, just rebuild the whole list
                     this.setData(proxies.map(id => fields[id] as T));
@@ -98,7 +103,7 @@ export class ListField<T extends Field> extends BasicField<T[]> {
                 this._processingServerUpdate = false;
             }
             callback(this);
-        }))
+        }));
     }
 
     ToScriptString(): string {
@@ -114,12 +119,12 @@ export class ListField<T extends Field> extends BasicField<T[]> {
             type: Types.List,
             data: this._proxies || [],
             _id: this.Id
-        }
+        };
     }
 
     static FromJson(id: string, ids: string[]): ListField<Field> {
         let list = new ListField([], id, false);
         list._proxies = ids;
-        return list
+        return list;
     }
 }
\ No newline at end of file
diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts
index e0c8648de..45b920e31 100644
--- a/src/fields/NumberField.ts
+++ b/src/fields/NumberField.ts
@@ -1,4 +1,4 @@
-import { BasicField } from "./BasicField"
+import { BasicField } from "./BasicField";
 import { Types } from "../server/Message";
 import { FieldId } from "./Field";
 
@@ -20,6 +20,6 @@ export class NumberField extends BasicField<number> {
             _id: this.Id,
             type: Types.Number,
             data: this.Data
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
index 436c1cf2b..65e179894 100644
--- a/src/fields/PDFField.ts
+++ b/src/fields/PDFField.ts
@@ -1,6 +1,6 @@
 import { BasicField } from "./BasicField";
 import { Field, FieldId } from "./Field";
-import { observable } from "mobx"
+import { observable } from "mobx";
 import { Types } from "../server/Message";
 
 
@@ -27,7 +27,7 @@ export class PDFField extends BasicField<URL> {
             type: Types.PDF,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
     @observable
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 5efb43314..6f7b3074a 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -20,7 +20,7 @@ export class RichTextField extends BasicField<string> {
             type: Types.RichText,
             data: this.Data,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts
index 71d8ea310..69d26f42f 100644
--- a/src/fields/TextField.ts
+++ b/src/fields/TextField.ts
@@ -1,4 +1,4 @@
-import { BasicField } from "./BasicField"
+import { BasicField } from "./BasicField";
 import { FieldId } from "./Field";
 import { Types } from "../server/Message";
 
@@ -20,6 +20,6 @@ export class TextField extends BasicField<string> {
             type: Types.Text,
             data: this.Data,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts
index e2162c751..ad0f6f350 100644
--- a/src/fields/TupleField.ts
+++ b/src/fields/TupleField.ts
@@ -21,7 +21,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
                 UndoManager.AddEvent({
                     undo: () => this.Data[change.index] = change.oldValue,
                     redo: () => this.Data[change.index] = change.newValue
-                })
+                });
                 Server.UpdateField(this);
             } else {
                 throw new Error("Why are you messing with the length of a tuple, huh?");
@@ -31,7 +31,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
 
     protected setData(value: [T, U]) {
         if (this.observeDisposer) {
-            this.observeDisposer()
+            this.observeDisposer();
         }
         this.data = observable(value) as (T | U)[] as [T, U];
         this.observeTuple();
@@ -54,6 +54,6 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
             type: Types.Tuple,
             data: this.Data,
             _id: this.Id
-        }
+        };
     }
 }
\ No newline at end of file
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
index 992cc1641..d7cd7e968 100644
--- a/src/fields/VideoField.ts
+++ b/src/fields/VideoField.ts
@@ -24,7 +24,7 @@ export class VideoField extends BasicField<URL> {
             type: Types.Video,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts
index 0cbcc6d33..6023e9e6b 100644
--- a/src/fields/WebField.ts
+++ b/src/fields/WebField.ts
@@ -24,7 +24,7 @@ export class WebField extends BasicField<URL> {
             type: Types.Web,
             data: this.Data.href,
             _id: this.Id
-        }
+        };
     }
 
 }
\ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index c3684a0eb..ec89a1194 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -22,7 +22,7 @@ import { Opt } from '../fields/Field';
 // }
 
 const onFileLoad = async (file: any) => {
-    let imgPrev = document.getElementById("img_preview")
+    let imgPrev = document.getElementById("img_preview");
     if (imgPrev) {
         let files: File[] = file.target.files;
         if (files.length !== 0) {
@@ -30,15 +30,15 @@ const onFileLoad = async (file: any) => {
             let formData = new FormData();
             formData.append("file", files[0]);
 
-            const upload = window.location.origin + "/upload"
+            const upload = window.location.origin + "/upload";
             const res = await fetch(upload, {
                 method: 'POST',
                 body: formData
             });
             const json = await res.json();
             json.map(async (file: any) => {
-                let path = window.location.origin + file
-                var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 })
+                let path = window.location.origin + file;
+                var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 });
 
                 const res = await rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId));
                 if (!res) {
@@ -47,12 +47,12 @@ const onFileLoad = async (file: any) => {
                 const field = await Server.GetField(res);
                 let pending: Opt<Document>;
                 if (field instanceof Document) {
-                    pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document)
+                    pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document);
                 }
                 if (pending) {
                     pending.GetOrCreateAsync(KeyStore.Data, ListField, list => {
                         list.Data.push(doc);
-                    })
+                    });
                 }
             });
 
@@ -61,7 +61,7 @@ const onFileLoad = async (file: any) => {
             //imgPrev.setAttribute("src", window.location.origin + files[0].name)
         }
     }
-}
+};
 
 ReactDOM.render((
     <div className="imgupload_cont">
diff --git a/src/server/Client.ts b/src/server/Client.ts
index 6b8841658..02402a5a0 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -2,14 +2,14 @@ import { computed } from "mobx";
 
 export class Client {
     constructor(guid: string) {
-        this.guid = guid
+        this.guid = guid;
     }
 
     private guid: string;
 
     @computed
     public get GUID(): string {
-        return this.guid
+        return this.guid;
     }
 
 }
\ No newline at end of file
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts
index b6fe15655..d42741410 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/config/passport.ts
@@ -1,4 +1,4 @@
-import * as passport from 'passport'
+import * as passport from 'passport';
 import * as passportLocal from 'passport-local';
 import * as mongodb from 'mongodb';
 import * as _ from "lodash";
@@ -22,7 +22,7 @@ passport.deserializeUser<any, any>((id, done) => {
 passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
     User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => {
         if (error) return done(error);
-        if (!user) return done(undefined, false, { message: "Invalid email or password" }) // invalid email
+        if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email
         user.comparePassword(password, (error: Error, isMatch: boolean) => {
             if (error) return done(error);
             if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password
@@ -37,7 +37,7 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) =
         return next();
     }
     return res.redirect(RouteStore.login);
-}
+};
 
 export let isAuthorized = (req: Request, res: Response, next: NextFunction) => {
     const provider = req.path.split("/").slice(-1)[0];
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
index 835432c8e..b08c1aebe 100644
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ b/src/server/authentication/controllers/WorkspacesMenu.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { observable, action, configure, reaction, computed, ObservableMap, runInAction } from 'mobx';
 import { observer } from "mobx-react";
-import './WorkspacesMenu.css'
+import './WorkspacesMenu.css';
 import { Document } from '../../../fields/Document';
 import { EditableView } from '../../../client/views/EditableView';
 import { KeyStore } from '../../../fields/KeyStore';
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
index 2bbb334b5..1dacdf3fa 100644
--- a/src/server/authentication/controllers/user_controller.ts
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -109,12 +109,12 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
     }
 
     passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
-        if (err) { next(err); return }
+        if (err) { next(err); return; }
         if (!user) {
             return res.redirect(RouteStore.signup);
         }
         req.logIn(user, (err) => {
-            if (err) { next(err); return }
+            if (err) { next(err); return; }
             res.redirect(RouteStore.home);
         });
     })(req, res, next);
@@ -132,14 +132,14 @@ export let getLogout = (req: Request, res: Response) => {
         sess.destroy((err) => { if (err) { console.log(err); } });
     }
     res.redirect(RouteStore.login);
-}
+};
 
 export let getForgot = function (req: Request, res: Response) {
     res.render("forgot.pug", {
         title: "Recover Password",
         user: req.user,
     });
-}
+};
 
 export let postForgot = function (req: Request, res: Response, next: NextFunction) {
     const email = req.body.email;
@@ -152,14 +152,14 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
                     return;
                 }
                 done(null, buffer.toString('hex'));
-            })
+            });
         },
         function (token: string, done: any) {
             User.findOne({ email }, function (err, user: DashUserModel) {
                 if (!user) {
                     // NO ACCOUNT WITH SUBMITTED EMAIL
                     res.redirect(RouteStore.forgot);
-                    return
+                    return;
                 }
                 user.passwordResetToken = token;
                 user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
@@ -193,8 +193,8 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
     ], function (err) {
         if (err) return next(err);
         res.redirect(RouteStore.forgot);
-    })
-}
+    });
+};
 
 export let getReset = function (req: Request, res: Response) {
     User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
@@ -206,7 +206,7 @@ export let getReset = function (req: Request, res: Response) {
             user: req.user,
         });
     });
-}
+};
 
 export let postReset = function (req: Request, res: Response) {
     async.waterfall([
@@ -263,4 +263,4 @@ export let postReset = function (req: Request, res: Response) {
     ], function (err) {
         res.redirect(RouteStore.login);
     });
-}
\ No newline at end of file
+};
\ 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 0ac85b446..13eddafbf 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -70,7 +70,7 @@ export class CurrentUserUtils {
         let doc = new Document(id);
 
         doc.Set(KeyStore.Workspaces, new ListField<Document>());
-        doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }))
+        doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }));
         return doc;
     }
 
@@ -81,7 +81,7 @@ export class CurrentUserUtils {
                 CurrentUserUtils.curr_id = obj.id as string;
                 CurrentUserUtils.curr_email = obj.email as string;
             } else {
-                throw new Error("There should be a user! Why does Dash think there isn't one?")
+                throw new Error("There should be a user! Why does Dash think there isn't one?");
             }
         });
         let userDocPromise = rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(id => {
@@ -92,9 +92,9 @@ export class CurrentUserUtils {
                     } else {
                         this.user_document = this.createUserDocument(id);
                     }
-                })
+                });
             } else {
-                throw new Error("There should be a user id! Why does Dash think there isn't one?")
+                throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
         });
         return Promise.all([userPromise, userDocPromise]);
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index 81580aad5..1c6926517 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -2,7 +2,7 @@
 import * as bcrypt from "bcrypt-nodejs";
 //@ts-ignore
 import * as mongoose from "mongoose";
-var url = 'mongodb://localhost:27017/Dash'
+var url = 'mongodb://localhost:27017/Dash';
 
 mongoose.connect(url, { useNewUrlParser: true });
 
diff --git a/src/server/database.ts b/src/server/database.ts
index 415acc09a..0bc806253 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,15 +1,15 @@
 import * as mongodb from 'mongodb';
 
 export class Database {
-    public static Instance = new Database()
+    public static Instance = new Database();
     private MongoClient = mongodb.MongoClient;
     private url = 'mongodb://localhost:27017/Dash';
     private db?: mongodb.Db;
 
     constructor() {
         this.MongoClient.connect(this.url, (err, client) => {
-            this.db = client.db()
-        })
+            this.db = client.db();
+        });
     }
 
     private currentWrites: { [_id: string]: Promise<void> } = {};
@@ -30,19 +30,19 @@ export class Database {
                     //     console.log(JSON.stringify(res.result));
                     // }
                     if (this.currentWrites[id] === promise) {
-                        delete this.currentWrites[id]
+                        delete this.currentWrites[id];
                     }
                     if (resolve) {
                         resolve();
                     }
                     callback();
                 });
-            }
+            };
             if (prom) {
                 const newProm: Promise<void> = prom.then(() => run(newProm));
                 this.currentWrites[id] = newProm;
             } else {
-                const newProm: Promise<void> = new Promise<void>(res => run(newProm, res))
+                const newProm: Promise<void> = new Promise<void>(res => run(newProm, res));
                 this.currentWrites[id] = newProm;
             }
         }
@@ -61,7 +61,7 @@ export class Database {
                 let collection = this.db.collection(collectionName);
                 collection.deleteMany({}, res);
             }
-        })
+        });
     }
 
     public insert(kvpairs: any) {
@@ -70,7 +70,7 @@ export class Database {
             collection.insertOne(kvpairs, (err: any, res: any) => {
                 if (err) {
                     // console.log(err)
-                    return
+                    return;
                 }
             });
         }
@@ -81,30 +81,30 @@ export class Database {
         if (this.db) {
             let collection = this.db.collection('documents');
             collection.findOne({ _id: id }, (err: any, res: any) => {
-                result = res
+                result = res;
                 if (!result) {
-                    fn(undefined)
+                    fn(undefined);
                 }
-                fn(result)
-            })
-        };
+                fn(result);
+            });
+        }
     }
 
     public getDocuments(ids: string[], fn: (res: any) => void) {
         if (this.db) {
             let collection = this.db.collection('documents');
-            let cursor = collection.find({ _id: { "$in": ids } })
+            let cursor = collection.find({ _id: { "$in": ids } });
             cursor.toArray((err, docs) => {
                 if (err) {
                     console.log(err.message);
                     console.log(err.errmsg);
                 }
                 fn(docs);
-            })
-        };
+            });
+        }
     }
 
     public print() {
-        console.log("db says hi!")
+        console.log("db says hi!");
     }
 }
diff --git a/src/server/index.ts b/src/server/index.ts
index f60e6e293..b9c7448b4 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,10 +1,10 @@
-import * as express from 'express'
-const app = express()
-import * as webpack from 'webpack'
+import * as express from 'express';
+const app = express();
+import * as webpack from 'webpack';
 import * as wdm from 'webpack-dev-middleware';
 import * as whm from 'webpack-hot-middleware';
-import * as path from 'path'
-import * as formidable from 'formidable'
+import * as path from 'path';
+import * as formidable from 'formidable';
 import * as passport from 'passport';
 import { MessageStore, Transferable } from "./Message";
 import { Client } from './Client';
@@ -13,7 +13,7 @@ import { Utils } from '../Utils';
 import { ObservableMap } from 'mobx';
 import { FieldId, Field } from '../fields/Field';
 import { Database } from './database';
-import * as io from 'socket.io'
+import * as io from 'socket.io';
 import { getLogin, postLogin, getSignup, postSignup, getLogout, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller';
 const config = require('../../webpack.config');
 const compiler = webpack(config);
@@ -31,19 +31,19 @@ const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 import { DashUserModel } from './authentication/models/user_model';
 import * as fs from 'fs';
-import * as request from 'request'
+import * as request from 'request';
 import { RouteStore } from './RouteStore';
-import { exec } from 'child_process'
+import { exec } from 'child_process';
 
 const download = (url: string, dest: fs.PathLike) => {
     request.get(url).pipe(fs.createWriteStream(dest));
-}
+};
 
 const mongoUrl = 'mongodb://localhost:27017/Dash';
-mongoose.connect(mongoUrl)
+mongoose.connect(mongoUrl);
 mongoose.connection.on('connected', function () {
     console.log("connected");
-})
+});
 
 // SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE
 // ORDER OF IMPORTS MATTERS
@@ -73,7 +73,7 @@ app.use((req, res, next) => {
 
 app.get("/hello", (req, res) => {
     res.send("<p>Hello</p>");
-})
+});
 
 enum Method {
     GET,
@@ -98,7 +98,7 @@ function addSecureRoute(method: Method,
         const dashUser: DashUserModel = req.user;
         if (!dashUser) return onRejection(res);
         handler(dashUser, res, req);
-    }
+    };
     subscribers.forEach(route => {
         switch (method) {
             case Method.GET:
@@ -116,7 +116,7 @@ function addSecureRoute(method: Method,
 let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
 
 app.use(express.static(__dirname + RouteStore.public));
-app.use(RouteStore.images, express.static(__dirname + RouteStore.public))
+app.use(RouteStore.images, express.static(__dirname + RouteStore.public));
 
 app.get("/pull", (req, res) => {
     exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => {
@@ -125,7 +125,7 @@ app.get("/pull", (req, res) => {
             return;
         }
         res.redirect("/");
-    })
+    });
 });
 
 // GETTERS
@@ -178,13 +178,13 @@ addSecureRoute(
 addSecureRoute(
     Method.POST,
     (user, res, req) => {
-        let form = new formidable.IncomingForm()
-        form.uploadDir = __dirname + "/public/files/"
-        form.keepExtensions = true
+        let form = new formidable.IncomingForm();
+        form.uploadDir = __dirname + "/public/files/";
+        form.keepExtensions = true;
         // let path = req.body.path;
-        console.log("upload")
+        console.log("upload");
         form.parse(req, (err, fields, files) => {
-            console.log("parsing")
+            console.log("parsing");
             let names: any[] = [];
             for (const name in files) {
                 let file = files[name];
@@ -211,8 +211,8 @@ app.post(RouteStore.login, postLogin);
 app.get(RouteStore.logout, getLogout);
 
 // FORGOT PASSWORD EMAIL HANDLING
-app.get(RouteStore.forgot, getForgot)
-app.post(RouteStore.forgot, postForgot)
+app.get(RouteStore.forgot, getForgot);
+app.post(RouteStore.forgot, postForgot);
 
 // RESET PASSWORD EMAIL HANDLING
 app.get(RouteStore.reset, getReset);
@@ -232,32 +232,32 @@ app.get(RouteStore.deleteAll, (req, res) => {
 
 app.use(wdm(compiler, {
     publicPath: config.output.publicPath
-}))
+}));
 
-app.use(whm(compiler))
+app.use(whm(compiler));
 
 // start the Express server
 app.listen(port, () => {
     console.log(`server started at http://localhost:${port}`);
-})
+});
 
 const server = io();
 interface Map {
     [key: string]: Client;
 }
-let clients: Map = {}
+let clients: Map = {};
 
 server.on("connection", function (socket: Socket) {
-    console.log("a user has connected")
+    console.log("a user has connected");
 
-    Utils.Emit(socket, MessageStore.Foo, "handshooken")
+    Utils.Emit(socket, MessageStore.Foo, "handshooken");
 
-    Utils.AddServerHandler(socket, MessageStore.Bar, barReceived)
-    Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args))
-    Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField)
-    Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields)
-    Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields)
-})
+    Utils.AddServerHandler(socket, MessageStore.Bar, barReceived);
+    Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+    Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+    Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+    Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+});
 
 function deleteFields() {
     return Database.Instance.deleteAll();
@@ -276,12 +276,12 @@ function barReceived(guid: String) {
 function getField([id, callback]: [string, (result: any) => void]) {
     Database.Instance.getDocument(id, (result: any) => {
         if (result) {
-            callback(result)
+            callback(result);
         }
         else {
-            callback(undefined)
+            callback(undefined);
         }
-    })
+    });
 }
 
 function getFields([ids, callback]: [string[], (result: any) => void]) {
@@ -291,7 +291,7 @@ function getFields([ids, callback]: [string[], (result: any) => void]) {
 function setField(socket: Socket, newValue: Transferable) {
     Database.Instance.update(newValue._id, newValue, () => {
         socket.broadcast.emit(MessageStore.SetField.Message, newValue);
-    })
+    });
 }
 
 server.listen(serverPort);
diff --git a/test/test.ts b/test/test.ts
index db24cae5f..16cace026 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -1,6 +1,6 @@
 import { NumberField } from "../src/fields/NumberField";
 import { expect } from 'chai';
-import 'mocha'
+import 'mocha';
 import { Key } from "../src/fields/Key";
 import { Document } from "../src/fields/Document";
 import { autorun, reaction } from "mobx";
@@ -17,7 +17,7 @@ describe('Number Controller', () => {
     it('Should update', () => {
         const numController = new NumberField(15);
         let ran = false;
-        reaction(() => numController.Data, (data) => { ran = true; })
+        reaction(() => numController.Data, (data) => { ran = true; });
         expect(ran).to.equal(false);
         numController.Data = 5;
         expect(ran).to.equal(true);
@@ -42,7 +42,7 @@ describe("Document", () => {
         let key = new Key("Test");
         let key2 = new Key("Test2");
         let ran = false;
-        reaction(() => doc.Get(key), (field) => { ran = true });
+        reaction(() => doc.Get(key), (field) => { ran = true; });
         expect(ran).to.equal(false);
 
         doc.Set(key2, new NumberField(4));
diff --git a/tslint.json b/tslint.json
index 54876916e..aa4dee4e5 100644
--- a/tslint.json
+++ b/tslint.json
@@ -27,14 +27,14 @@
         "arrow-return-shorthand": true,
         // "object-literal-shorthand": true,
         // "object-literal-sort-keys": true,
-        // "semicolon": [
-        //     true,
-        //     "always"
-        // ],
-        // "curly": [
-        //     true,
-        //     "ignore-same-line"
-        // ],
+        "semicolon": [
+            true,
+            "always"
+        ],
+        "curly": [
+            true,
+            "ignore-same-line"
+        ],
         // "quotemark": [
         //     true,
         //     "double",
-- 
cgit v1.2.3-70-g09d2


From a12bc4e408119ca2c310c04598c3aa2b424751c5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 14 Apr 2019 15:10:12 -0400
Subject: restored ContainingCollectionView for use in drawings links (maybe
 brushing later)

---
 src/client/views/Main.tsx                          |  2 +-
 .../views/collections/CollectionSchemaView.tsx     |  4 +++-
 src/client/views/collections/CollectionSubView.tsx |  2 ++
 src/client/views/collections/CollectionView.tsx    |  8 +++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 ++--
 src/client/views/nodes/DocumentContentsView.tsx    | 28 ++++++++++++----------
 src/client/views/nodes/FieldView.tsx               |  7 ++++--
 src/client/views/nodes/KeyValuePair.tsx            |  2 ++
 9 files changed, 36 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index babd3be2a..03cfbe9da 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -282,7 +282,7 @@ export class Main extends React.Component {
             return <div className="mainDiv-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
                 <div className="mainDiv-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
-                        selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+                        selectOnLoad={true} ContainingCollectionView={undefined} CollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
                 </div>
             </ div>;
         }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2a1d1dd94..63346a4c6 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -75,6 +75,8 @@ export class CollectionSchemaView extends CollectionSubView {
         let props: FieldViewProps = {
             Document: rowProps.value[0],
             fieldKey: rowProps.value[1],
+            CollectionView: this.props.CollectionView,
+            ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
             select: emptyFunction,
             isTopMost: false,
@@ -313,7 +315,7 @@ export class CollectionSchemaView extends CollectionSubView {
                                 ContentScaling={this.getContentScaling}
                                 PanelWidth={this.getPanelWidth}
                                 PanelHeight={this.getPanelHeight}
-                                ContainingCollectionView={undefined}
+                                ContainingCollectionView={this.props.CollectionView}
                                 focus={emptyDocFunction}
                                 parentActive={this.props.active}
                                 onActiveChanged={this.props.onActiveChanged} /> : null}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d91db68bb..61b7787e7 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -16,6 +16,7 @@ import { Server } from "../../Server";
 import { FieldViewProps } from "../nodes/FieldView";
 import * as rp from 'request-promise';
 import { emptyFunction } from "../../../Utils";
+import { CollectionView } from "./CollectionView";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
@@ -24,6 +25,7 @@ export interface CollectionViewProps extends FieldViewProps {
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
+    CollectionView: Opt<CollectionView>;
 }
 
 export type CursorEntry = TupleField<[string, string], [number, number]>;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8abd0a02d..1f69a69e8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,12 +18,12 @@ export class CollectionView extends React.Component<FieldViewProps> {
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (type) {
-            case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />);
-            case CollectionViewType.Docking: return (<CollectionDockingView {...props} />);
-            case CollectionViewType.Tree: return (<CollectionTreeView {...props} />);
+            case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
+            case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
+            case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
             case CollectionViewType.Freeform:
             default:
-                return (<CollectionFreeFormView {...props} />);
+                return (<CollectionFreeFormView {...props} CollectionView={this} />);
         }
         return (null);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 94945bde6..e9e942b78 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -274,7 +274,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             PanelWidth: document.Width,
             PanelHeight: document.Height,
             ContentScaling: this.noScaling,
-            ContainingCollectionView: undefined,
+            ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
             parentActive: this.props.active,
             onActiveChanged: this.props.active,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a4722a6f8..cf75aef40 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -209,10 +209,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let selRect = this.Bounds;
         let selection: Document[] = [];
         this.props.activeDocuments().map(doc => {
+            var z = doc.GetNumber(KeyStore.Zoom, 1);
             var x = doc.GetNumber(KeyStore.X, 0);
             var y = doc.GetNumber(KeyStore.Y, 0);
-            var w = doc.GetNumber(KeyStore.Width, 0);
-            var h = doc.GetNumber(KeyStore.Height, 0);
+            var w = doc.GetNumber(KeyStore.Width, 0) / z;
+            var h = doc.GetNumber(KeyStore.Height, 0) / z;
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5836da396..a967db000 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -44,19 +44,19 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
 
     CreateBindings(): JsxBindings {
-        let
-            {
-                Document,
-                isSelected,
-                select,
-                isTopMost,
-                selectOnLoad,
-                ScreenToLocalTransform,
-                addDocument,
-                removeDocument,
-                onActiveChanged,
-                parentActive: active,
-            } = this.props;
+        let {
+            Document,
+            isSelected,
+            select,
+            isTopMost,
+            selectOnLoad,
+            ScreenToLocalTransform,
+            ContainingCollectionView,
+            addDocument,
+            removeDocument,
+            onActiveChanged,
+            parentActive: active,
+        } = this.props;
         let bindings: JsxBindings = {
             props: {
                 Document,
@@ -65,6 +65,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 isTopMost,
                 selectOnLoad,
                 ScreenToLocalTransform,
+                CollectionView: undefined,
+                ContainingCollectionView,
                 active,
                 onActiveChanged,
                 addDocument,
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index e0c5e19fb..755367d23 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,7 +1,7 @@
 import React = require("react");
 import { observer } from "mobx-react";
 import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field";
 import { Document } from "../../../fields/Document";
 import { TextField } from "../../../fields/TextField";
 import { NumberField } from "../../../fields/NumberField";
@@ -20,6 +20,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
 import { KeyStore } from "../../../fields/KeyStore";
 import { returnFalse, emptyDocFunction } from "../../../Utils";
+import { CollectionView } from "../collections/CollectionView";
 
 
 //
@@ -29,6 +30,8 @@ import { returnFalse, emptyDocFunction } from "../../../Utils";
 //
 export interface FieldViewProps {
     fieldKey: Key;
+    ContainingCollectionView: Opt<CollectionView>;
+    CollectionView: Opt<CollectionView>;
     Document: Document;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
@@ -90,7 +93,7 @@ export class FieldView extends React.Component<FieldViewProps> {
                     isSelected={returnFalse}
                     select={returnFalse}
                     layoutKey={KeyStore.Layout}
-                    ContainingCollectionView={undefined}
+                    ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
                     onActiveChanged={this.props.onActiveChanged} />
             );
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3e0b61c3d..1b9dd26bd 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -47,6 +47,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         }
         let props: FieldViewProps = {
             Document: this.props.doc,
+            CollectionView: undefined,
+            ContainingCollectionView: undefined,
             fieldKey: this.key,
             isSelected: returnFalse,
             select: emptyFunction,
-- 
cgit v1.2.3-70-g09d2


From 2cb379ebe0fd8f2dc9ffa64c2b303bef648cfa33 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 14 Apr 2019 16:59:21 -0400
Subject: cleaned up ContainingCollectionView stuff a bit

---
 src/client/util/DocumentManager.ts                 | 22 ++++------
 src/client/views/Main.tsx                          |  2 +-
 src/client/views/collections/CollectionPDFView.tsx |  4 +-
 .../views/collections/CollectionSchemaView.tsx     |  1 -
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../views/collections/CollectionVideoView.tsx      |  2 +-
 .../CollectionFreeFormLinksView.tsx                | 21 +++++-----
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentView.tsx            | 12 +++---
 src/client/views/nodes/FieldView.tsx               |  5 ++-
 src/client/views/nodes/KeyValuePair.scss           | 40 +++++++++---------
 src/client/views/nodes/KeyValuePair.tsx            | 47 +++++++++-------------
 12 files changed, 71 insertions(+), 90 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3e093c8dc..3b5a5b470 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,11 +1,9 @@
-import React = require('react');
-import { observer } from 'mobx-react';
-import { observable, action, computed } from 'mobx';
+import { computed, observable } from 'mobx';
 import { Document } from "../../fields/Document";
-import { DocumentView } from '../views/nodes/DocumentView';
-import { KeyStore } from '../../fields/KeyStore';
 import { FieldWaiting } from '../../fields/Field';
+import { KeyStore } from '../../fields/KeyStore';
 import { ListField } from '../../fields/ListField';
+import { DocumentView } from '../views/nodes/DocumentView';
 
 
 export class DocumentManager {
@@ -27,10 +25,6 @@ export class DocumentManager {
         // this.DocumentViews = new Array<DocumentView>();
     }
 
-    public getAllDocumentViews(collection: Document) {
-        return this.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection);
-    }
-
     public getDocumentView(toFind: Document): DocumentView | null {
 
         let toReturn: DocumentView | null;
@@ -39,7 +33,6 @@ export class DocumentManager {
         //gets document view that is in a freeform canvas collection
         DocumentManager.Instance.DocumentViews.map(view => {
             let doc = view.props.Document;
-            // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
 
             if (doc === toFind) {
                 toReturn = view;
@@ -51,7 +44,7 @@ export class DocumentManager {
             }
         });
 
-        return (toReturn);
+        return toReturn;
     }
     public getDocumentViews(toFind: Document): DocumentView[] {
 
@@ -72,7 +65,7 @@ export class DocumentManager {
             }
         });
 
-        return (toReturn);
+        return toReturn;
     }
 
     @computed
@@ -84,9 +77,8 @@ export class DocumentManager {
                     if (link instanceof Document) {
                         let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
                         if (linkToDoc && linkToDoc !== FieldWaiting) {
-                            DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                                pairs.push({ a: dv, b: docView1, l: link });
-                            });
+                            DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 =>
+                                pairs.push({ a: dv, b: docView1, l: link }));
                         }
                     }
                     return pairs;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 03cfbe9da..f8c6ec0e2 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -282,7 +282,7 @@ export class Main extends React.Component {
             return <div className="mainDiv-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
                 <div className="mainDiv-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
-                        selectOnLoad={true} ContainingCollectionView={undefined} CollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+                        selectOnLoad={true} ContainingCollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
                 </div>
             </ div>;
         }
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 6cbe59012..229bc4059 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
@@ -42,7 +42,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
         let props = { ...this.props, ...renderProps };
         return (
             <>
-                <CollectionFreeFormView {...props} />
+                <CollectionFreeFormView {...props} CollectionView={this} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
             </>
         );
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 63346a4c6..2c99c9c67 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -75,7 +75,6 @@ export class CollectionSchemaView extends CollectionSubView {
         let props: FieldViewProps = {
             Document: rowProps.value[0],
             fieldKey: rowProps.value[1],
-            CollectionView: this.props.CollectionView,
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 61b7787e7..1c927c78d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -17,6 +17,8 @@ import { FieldViewProps } from "../nodes/FieldView";
 import * as rp from 'request-promise';
 import { emptyFunction } from "../../../Utils";
 import { CollectionView } from "./CollectionView";
+import { CollectionPDFView } from "./CollectionPDFView";
+import { CollectionVideoView } from "./CollectionVideoView";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
@@ -25,7 +27,7 @@ export interface CollectionViewProps extends FieldViewProps {
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
-    CollectionView: Opt<CollectionView>;
+    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
 }
 
 export type CursorEntry = TupleField<[string, string], [number, number]>;
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 6c9780adb..29fb342c6 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -109,7 +109,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
         let props = { ...this.props, ...renderProps };
         return (
             <>
-                <CollectionFreeFormView {...props} />
+                <CollectionFreeFormView {...props} CollectionView={this} />
                 {this.props.isSelected() ? this.uIButtons : (null)}
             </>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 647c83d4d..2f684a54e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,7 +1,6 @@
-import { computed, reaction, trace, IReactionDisposer } from "mobx";
+import { computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
 import { KeyStore } from "../../../../fields/KeyStore";
 import { ListField } from "../../../../fields/ListField";
 import { Utils } from "../../../../Utils";
@@ -85,19 +84,17 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
             let targetViews = this.documentAnchors(connection.b);
             let possiblePairs: { a: Document, b: Document, }[] = [];
             srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
-            possiblePairs.map(possiblePair => {
-                if (!drawnPairs.reduce((found, drawnPair) => {
+            possiblePairs.map(possiblePair =>
+                drawnPairs.reduce((found, drawnPair) => {
                     let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
-                    if (match) {
-                        if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) {
-                            drawnPair.l.push(connection.l);
-                        }
+                    if (match && !drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) {
+                        drawnPair.l.push(connection.l);
                     }
                     return match || found;
-                }, false)) {
-                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] });
-                }
-            });
+                }, false)
+                ||
+                drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+            );
             return drawnPairs;
         }, [] as { a: Document, b: Document, l: Document[] }[]);
         return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a967db000..88900c4b1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -65,7 +65,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 isTopMost,
                 selectOnLoad,
                 ScreenToLocalTransform,
-                CollectionView: undefined,
                 ContainingCollectionView,
                 active,
                 onActiveChanged,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f0f9af89f..935540af1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,30 +1,32 @@
-import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx";
+import { action, computed, runInAction } from "mobx";
 import { observer } from "mobx-react";
+import { BooleanField } from "../../../fields/BooleanField";
 import { Document } from "../../../fields/Document";
 import { Field, FieldWaiting, Opt } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { BooleanField } from "../../../fields/BooleanField";
 import { TextField } from "../../../fields/TextField";
 import { ServerUtils } from "../../../server/ServerUtil";
-import { Utils, emptyFunction } from "../../../Utils";
+import { emptyFunction, Utils } from "../../../Utils";
 import { Documents } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-import { undoBatch, UndoManager } from "../../util/UndoManager";
 
 
 export interface DocumentViewProps {
-    ContainingCollectionView: Opt<CollectionView>;
+    ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Document;
     opacity: number;
     addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 755367d23..562de4827 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -21,6 +21,8 @@ import { Transform } from "../../util/Transform";
 import { KeyStore } from "../../../fields/KeyStore";
 import { returnFalse, emptyDocFunction } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
 
 
 //
@@ -30,8 +32,7 @@ import { CollectionView } from "../collections/CollectionView";
 //
 export interface FieldViewProps {
     fieldKey: Key;
-    ContainingCollectionView: Opt<CollectionView>;
-    CollectionView: Opt<CollectionView>;
+    ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Document;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 04d002c7b..01701e02c 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,30 +1,28 @@
 @import "../globalCssVariables";
 
-.container{
-    width:100%;
-    height:100%;
-    display: flex;                 
-    flex-direction: row;           
-    flex-wrap: nowrap;              
-    justify-content: space-between; 
-}
 
 .keyValuePair-td-key {
     display:inline-block;
-    width: 50%;
+    .keyValuePair-td-key-container{
+        width:100%;
+        height:100%;
+        display: flex;                 
+        flex-direction: row;           
+        flex-wrap: nowrap;              
+        justify-content: space-between; 
+        .keyValuePair-td-key-delete{
+            position: relative;
+            background-color: transparent;
+            color:red;
+        }
+        .keyValuePair-keyField {
+            width:100%;
+            text-align: center;
+            position: relative;
+            overflow: auto;
+        }
+    }
 }
 .keyValuePair-td-value {
     display:inline-block;
-    width: 50%;
-}
-.keyValuePair-keyField {
-    width:100%;
-    text-align: center;
-    position: relative;
-    overflow: auto;
-}
-.delete{
-    position: relative;
-    background-color: transparent;
-    color:red;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 1b9dd26bd..5d69f23b2 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,18 +1,18 @@
-import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import "./KeyValueBox.scss";
-import "./KeyValuePair.scss";
-import React = require("react");
-import { FieldViewProps, FieldView } from './FieldView';
-import { Opt, Field } from '../../../fields/Field';
+import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
-import { observable, action } from 'mobx';
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
 import { Document } from '../../../fields/Document';
+import { Field, Opt } from '../../../fields/Field';
 import { Key } from '../../../fields/Key';
+import { emptyDocFunction, emptyFunction, returnFalse } from '../../../Utils';
 import { Server } from "../../Server";
-import { EditableView } from "../EditableView";
 import { CompileScript, ToField } from "../../util/Scripting";
 import { Transform } from '../../util/Transform';
-import { returnFalse, emptyFunction, emptyDocFunction } from '../../../Utils';
+import { EditableView } from "../EditableView";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./KeyValueBox.scss";
+import "./KeyValuePair.scss";
+import React = require("react");
 
 // Represents one row in a key value plane
 
@@ -25,29 +25,22 @@ export interface KeyValuePairProps {
 @observer
 export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
-    @observable
-    private key: Opt<Key>;
+    @observable private key: Opt<Key>;
 
     constructor(props: KeyValuePairProps) {
         super(props);
         Server.GetField(this.props.fieldId,
-            action((field: Opt<Field>) => {
-                if (field) {
-                    this.key = field as Key;
-                }
-            }));
+            action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
 
     }
 
 
     render() {
         if (!this.key) {
-            return <tr><td>error</td><td></td></tr>;
-
+            return <tr><td>error</td><td /></tr>;
         }
         let props: FieldViewProps = {
             Document: this.props.doc,
-            CollectionView: undefined,
             ContainingCollectionView: undefined,
             fieldKey: this.key,
             isSelected: returnFalse,
@@ -59,19 +52,17 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyDocFunction,
         };
-        let contents = (
-            <FieldView {...props} />
-        );
+        let contents = <FieldView {...props} />;
         return (
             <tr className={this.props.rowStyle}>
                 <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
-                    <div className="container">
-                        <button className="delete" onClick={() => {
+                    <div className="keyValuePair-td-key-container">
+                        <button className="keyValuePair-td-key-delete" onClick={() => {
                             let field = props.Document.Get(props.fieldKey);
-                            if (field && field instanceof Field) {
-                                props.Document.Set(props.fieldKey, undefined);
-                            }
-                        }}>X</button>
+                            field && field instanceof Field && props.Document.Set(props.fieldKey, undefined);
+                        }}>
+                            X
+                        </button>
                         <div className="keyValuePair-keyField">{this.key.Name}</div>
                     </div>
                 </td>
-- 
cgit v1.2.3-70-g09d2


From 6c0b421db6aa3204bbc6e42139d240f503000b5d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 15 Apr 2019 11:59:50 -0400
Subject: fixed zoom fading somewhat.

---
 src/Utils.ts                                       | 15 ++++++++++
 src/client/northstar/dash-fields/HistogramField.ts | 12 ++------
 src/client/views/Main.tsx                          |  3 +-
 src/client/views/MainOverlayTextBox.tsx            |  3 +-
 .../views/collections/CollectionDockingView.tsx    |  3 +-
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 19 ++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 18 ++++++++----
 src/client/views/nodes/DocumentContentsView.tsx    | 33 ++--------------------
 src/client/views/nodes/DocumentView.tsx            |  1 -
 src/client/views/nodes/FieldView.tsx               |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  4 ++-
 12 files changed, 51 insertions(+), 66 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index e07d4e82d..dec6245ef 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -87,10 +87,25 @@ export class Utils {
     }
 }
 
+export function OmitKeys(obj: any, keys: any, addKeyFunc?: (dup: any) => void) {
+    var dup: any = {};
+    for (var key in obj) {
+        if (keys.indexOf(key) === -1) {
+            dup[key] = obj[key];
+        }
+    }
+    addKeyFunc && addKeyFunc(dup);
+    return dup;
+}
+
 export function returnTrue() { return true; }
 
 export function returnFalse() { return false; }
 
+export function returnOne() { return 1; }
+
+export function returnZero() { return 0; }
+
 export function emptyFunction() { }
 
 export function emptyDocFunction(doc: Document) { }
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 82de51d56..c699691a4 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -6,6 +6,7 @@ import { BasicField } from "../../../fields/BasicField";
 import { Field, FieldId } from "../../../fields/Field";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { Types } from "../../../server/Message";
+import { OmitKeys } from "../../../Utils";
 
 
 export class HistogramField extends BasicField<HistogramOperation> {
@@ -13,17 +14,8 @@ export class HistogramField extends BasicField<HistogramOperation> {
         super(data ? data : HistogramOperation.Empty, save, id);
     }
 
-    omitKeys(obj: any, keys: any) {
-        var dup: any = {};
-        for (var key in obj) {
-            if (keys.indexOf(key) === -1) {
-                dup[key] = obj[key];
-            }
-        }
-        return dup;
-    }
     toString(): string {
-        return JSON.stringify(this.omitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']));
+        return JSON.stringify(OmitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']));
     }
 
     Copy(): Field {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index d26586216..98ef329c7 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -17,7 +17,7 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use
 import { MessageStore } from '../../server/Message';
 import { RouteStore } from '../../server/RouteStore';
 import { ServerUtils } from '../../server/ServerUtil';
-import { emptyDocFunction, emptyFunction, returnTrue, Utils } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne } from '../../Utils';
 import { Documents } from '../documents/Documents';
 import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
 import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
@@ -178,7 +178,6 @@ export class Main extends React.Component {
                         <DocumentView Document={mainCont}
                             addDocument={undefined}
                             removeDocument={undefined}
-                            opacity={1}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={noScaling}
                             PanelWidth={pwidthFunc}
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index df1addbc7..6d43d88f0 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -56,7 +56,8 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     @action
     textScroll = (e: React.UIEvent) => {
         if (this._textProxyDiv.current && this._textTargetDiv) {
-            this._textTargetDiv.scrollTop = this.TextScroll = this._textProxyDiv.current.children[0].scrollTop;
+            this.TextScroll = (e as any)._targetInst.stateNode.scrollTop;//  this._textProxyDiv.current.children[0].scrollTop;
+            this._textTargetDiv.scrollTop = this.TextScroll;
         }
     }
 
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4f7d4fc0d..2b96e7678 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,7 +8,7 @@ import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import Measure from "react-measure";
 import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field";
-import { Utils, returnTrue, emptyFunction, emptyDocFunction } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne } from "../../../Utils";
 import { Server } from "../../Server";
 import { undoBatch } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
@@ -343,7 +343,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 <DocumentView key={this._document.Id} Document={this._document}
                     addDocument={undefined}
                     removeDocument={undefined}
-                    opacity={1}
                     ContentScaling={this._contentScaling}
                     PanelWidth={this._nativeWidth}
                     PanelHeight={this._nativeHeight}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2c99c9c67..4f4068e7a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -12,7 +12,7 @@ import { Field, Opt } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
-import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils";
+import { emptyDocFunction, emptyFunction, returnFalse, returnOne } from "../../../Utils";
 import { Server } from "../../Server";
 import { SetupDrag } from "../../util/DragManager";
 import { CompileScript, ToField } from "../../util/Scripting";
@@ -308,7 +308,6 @@ export class CollectionSchemaView extends CollectionSubView {
                             <DocumentView Document={doc}
                                 addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
                                 isTopMost={false}
-                                opacity={1}
                                 selectOnLoad={false}
                                 ScreenToLocalTransform={this.getPreviewTransform}
                                 ContentScaling={this.getContentScaling}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cb4e8fb2e..4db3bf81b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
 import { KeyStore } from "../../../../fields/KeyStore";
 import { TextField } from "../../../../fields/TextField";
-import { emptyFunction, returnFalse } from "../../../../Utils";
+import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
 import { SelectionManager } from "../../../util/SelectionManager";
@@ -14,7 +14,7 @@ import { undoBatch } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
 import { InkingCanvas } from "../../InkingCanvas";
 import { Main } from "../../Main";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewProps } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionSubView, SubCollectionViewProps } from "../CollectionSubView";
@@ -200,7 +200,6 @@ export class CollectionFreeFormView extends CollectionSubView {
             this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
             this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
             e.stopPropagation();
-            e.preventDefault();
         }
     }
 
@@ -261,10 +260,9 @@ export class CollectionFreeFormView extends CollectionSubView {
         this.props.focus(this.props.Document);
     }
 
-    getDocumentViewProps(document: Document, opacity: number): DocumentViewProps {
+    getDocumentViewProps(document: Document): DocumentViewProps {
         return {
             Document: document,
-            opacity: opacity,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
@@ -288,9 +286,10 @@ export class CollectionFreeFormView extends CollectionSubView {
             var page = doc.GetNumber(KeyStore.Page, -1);
             var zoom = doc.GetNumber(KeyStore.Zoom, 1);
             var dv = DocumentManager.Instance.getDocumentView(doc);
-            let opacity = this.isAnnotationOverlay && (!dv || !SelectionManager.IsSelected(dv)) ? 1 - Math.abs(zoom - this.scale) : 1;
-            if ((page === curPage || page === -1)) {
-                prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc, opacity)} />);
+            let zoomFade = !this.isAnnotationOverlay || (dv && SelectionManager.IsSelected(dv)) ? 1 :
+                Math.max(0, 2 - (zoom < this.scale ? this.scale / zoom : zoom / this.scale));
+            if (page === curPage || page === -1) {
+                prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} zoomFade={zoomFade} />);
             }
             return prev;
         }, [] as JSX.Element[]);
@@ -305,13 +304,13 @@ export class CollectionFreeFormView extends CollectionSubView {
     @computed
     get backgroundView() {
         return !this.backgroundLayout ? (null) :
-            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document, 1)}
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
                 layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
     }
     @computed
     get overlayView() {
         return !this.overlayLayout ? (null) :
-            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document, 1)}
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
                 layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
     }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0aa152209..b00cefbf6 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed, trace } from "mobx";
 import { observer } from "mobx-react";
 import { KeyStore } from "../../../fields/KeyStore";
 import { NumberField } from "../../../fields/NumberField";
@@ -6,13 +6,17 @@ import { Transform } from "../../util/Transform";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
+import { OmitKeys } from "../../../Utils";
 
+export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
+    zoomFade: number;
+}
 
 @observer
-export class CollectionFreeFormDocumentView extends React.Component<DocumentViewProps> {
+export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
 
-    constructor(props: DocumentViewProps) {
+    constructor(props: CollectionFreeFormDocumentViewProps) {
         super(props);
     }
 
@@ -55,20 +59,24 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
 
     @computed
     get docView() {
-        return <DocumentView {...this.props}
+        return <DocumentView {...this.docViewProps}
             ContentScaling={this.contentScaling}
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
         />;
     }
+    @computed
+    get docViewProps(): DocumentViewProps {
+        return (OmitKeys(this.props, ['zoomFade']));
+    }
     panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
     panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
 
     render() {
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
-                opacity: this.props.opacity,
+                opacity: this.props.zoomFade,
                 transformOrigin: "left top",
                 transform: this.transform,
                 pointerEvents: "all",
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 88900c4b1..76f852601 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,7 +23,7 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
 import { Document } from "../../../fields/Document";
 import { FieldViewProps } from "./FieldView";
-import { Without } from "../../../Utils";
+import { Without, OmitKeys } from "../../../Utils";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -44,35 +44,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
 
     CreateBindings(): JsxBindings {
-        let {
-            Document,
-            isSelected,
-            select,
-            isTopMost,
-            selectOnLoad,
-            ScreenToLocalTransform,
-            ContainingCollectionView,
-            addDocument,
-            removeDocument,
-            onActiveChanged,
-            parentActive: active,
-        } = this.props;
-        let bindings: JsxBindings = {
-            props: {
-                Document,
-                isSelected,
-                select,
-                isTopMost,
-                selectOnLoad,
-                ScreenToLocalTransform,
-                ContainingCollectionView,
-                active,
-                onActiveChanged,
-                addDocument,
-                removeDocument,
-                focus,
-            }
-        };
+        let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
+
         for (const key of this.layoutKeys) {
             bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 935540af1..bcd746024 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -28,7 +28,6 @@ import React = require("react");
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Document;
-    opacity: number;
     addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
     removeDocument?: (doc: Document) => boolean;
     moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 562de4827..ebd25f937 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -19,7 +19,7 @@ import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
 import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction } from "../../../Utils";
+import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
@@ -83,7 +83,6 @@ export class FieldView extends React.Component<FieldViewProps> {
                 <DocumentContentsView Document={field}
                     addDocument={undefined}
                     removeDocument={undefined}
-                    opacity={1}
                     ScreenToLocalTransform={Transform.Identity}
                     ContentScaling={() => 1}
                     PanelWidth={() => 100}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 87c1bcb1e..ad1ed5df0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, reaction } from "mobx";
+import { action, IReactionDisposer, reaction, trace, computed } from "mobx";
 import { baseKeymap } from "prosemirror-commands";
 import { history, redo, undo } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
@@ -17,6 +17,7 @@ import React = require("react");
 import { TextField } from "../../../fields/TextField";
 import { KeyStore } from "../../../fields/KeyStore";
 import { MainOverlayTextBox } from "../MainOverlayTextBox";
+import { observer } from "mobx-react";
 const { buildMenuItems } = require("prosemirror-example-setup");
 const { menuBar } = require("prosemirror-menu");
 
@@ -41,6 +42,7 @@ export interface FormattedTextBoxOverlay {
     isOverlay?: boolean;
 }
 
+@observer
 export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> {
     public static LayoutString(fieldStr: string = "DataKey") {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
-- 
cgit v1.2.3-70-g09d2


From af6feb64510490da8d815f41ceb639d693b9eae3 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 19 Apr 2019 16:34:32 -0400
Subject: played with iconifying things in a different way.  fixed some things
 with schemas.

---
 src/Utils.ts                                       |  2 +-
 src/client/documents/Documents.ts                  | 16 ++++
 src/client/northstar/dash-nodes/HistogramBox.scss  |  6 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  2 +-
 src/client/views/DocumentDecorations.tsx           | 42 ++++++----
 src/client/views/Main.tsx                          |  8 +-
 .../views/collections/CollectionSchemaView.scss    |  9 ++-
 .../views/collections/CollectionSchemaView.tsx     |  5 +-
 .../CollectionFreeFormLinkView.tsx                 |  8 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 13 ++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  9 +--
 src/client/views/globalCssVariables.scss           |  2 +
 src/client/views/globalCssVariables.scss.d.ts      |  1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 26 +++----
 src/client/views/nodes/DocumentContentsView.tsx    |  3 +-
 src/client/views/nodes/DocumentView.scss           | 16 +---
 src/client/views/nodes/DocumentView.tsx            | 71 ++++++++---------
 src/client/views/nodes/FieldView.tsx               |  5 ++
 src/client/views/nodes/IconBox.scss                | 12 +++
 src/client/views/nodes/IconBox.tsx                 | 90 ++++++++++++++++++++++
 src/fields/IconFIeld.ts                            | 25 ++++++
 src/fields/KeyStore.ts                             | 10 +--
 src/server/Message.ts                              |  2 +-
 src/server/ServerUtil.ts                           |  2 +
 24 files changed, 264 insertions(+), 121 deletions(-)
 create mode 100644 src/client/views/nodes/IconBox.scss
 create mode 100644 src/client/views/nodes/IconBox.tsx
 create mode 100644 src/fields/IconFIeld.ts

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

diff --git a/src/Utils.ts b/src/Utils.ts
index dec6245ef..98f75d3b9 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,7 +1,7 @@
 import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
-import { Message, Types, Transferable } from './server/Message';
+import { Message } from './server/Message';
 import { Document } from './fields/Document';
 
 export class Utils {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4febfa7eb..b0bb74d89 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,6 +32,9 @@ 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 { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
+import { IconBox } from "../views/nodes/IconBox";
+import { IconField } from "../../fields/IconFIeld";
 
 export interface DocumentOptions {
     x?: number;
@@ -63,6 +66,7 @@ export namespace Documents {
     let videoProto: Document;
     let audioProto: Document;
     let pdfProto: Document;
+    let iconProto: Document;
     const textProtoId = "textProto";
     const histoProtoId = "histoProto";
     const pdfProtoId = "pdfProto";
@@ -72,6 +76,7 @@ export namespace Documents {
     const kvpProtoId = "kvpProto";
     const videoProtoId = "videoProto";
     const audioProtoId = "audioProto";
+    const iconProtoId = "iconProto";
 
     export function initProtos(): Promise<void> {
         return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => {
@@ -84,6 +89,7 @@ export namespace Documents {
             videoProto = fields[videoProtoId] as Document || CreateVideoPrototype();
             audioProto = fields[audioProtoId] as Document || CreateAudioPrototype();
             pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype();
+            iconProto = fields[iconProtoId] as Document || CreateIconPrototype();
         });
     }
     function assignOptions(doc: Document, options: DocumentOptions): Document {
@@ -92,6 +98,8 @@ export namespace Documents {
         if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
         if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
         if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
+        if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
+        if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
         if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
         if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
         if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
@@ -139,6 +147,11 @@ export namespace Documents {
         histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
         return histoProto;
     }
+    function CreateIconPrototype(): Document {
+        let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
+            { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data] });
+        return iconProto;
+    }
     function CreateTextPrototype(): Document {
         let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
             { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
@@ -203,6 +216,9 @@ export namespace Documents {
     export function TextDocument(options: DocumentOptions = {}) {
         return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options);
     }
+    export function IconDocument(icon: string, options: DocumentOptions = {}) {
+        return assignToDelegate(SetInstanceOptions(iconProto, { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data], layout: IconBox.LayoutString(), ...options }, [icon, IconField]), options);
+    }
     export function PdfDocument(url: string, options: DocumentOptions = {}) {
         return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options);
     }
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
index e899cf15e..06d781263 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -1,12 +1,12 @@
 .histogrambox-container {
     padding: 0vw;
     position: absolute;
-    top: 0;
-    left:0;
+    top: -50%;
+    left:-50%;
     text-align: center;
     width: 100%;
     height: 100%;
-    background: black;
+    background: black; 
   }
   .histogrambox-xaxislabel {
     position:absolute;
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 0e84ace50..e2ecc8c83 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -146,7 +146,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         return (
             <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
                 {({ measureRef }) =>
-                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}>
+                    <div className="histogrambox-container" ref={measureRef}>
                         <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
                             <span className="histogrambox-yaxislabel-text">
                                 {labelY}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index cfb9befd5..da2c7a3be 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,17 +5,18 @@ import { KeyStore } from "../../fields/KeyStore";
 import { ListField } from "../../fields/ListField";
 import { NumberField } from "../../fields/NumberField";
 import { TextField } from "../../fields/TextField";
+import { Document } from "../../fields/Document";
 import { emptyFunction } from "../../Utils";
 import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import './DocumentDecorations.scss';
 import { MainOverlayTextBox } from "./MainOverlayTextBox";
-import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { DocumentView } from "./nodes/DocumentView";
 import { LinkMenu } from "./nodes/LinkMenu";
 import React = require("react");
 import { CompileScript } from "../util/Scripting";
+import { IconBox } from "./nodes/IconBox";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -191,6 +192,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             document.addEventListener("pointerup", this.onMinimizeUp);
         }
     }
+
+    @observable _minimizedX = 0;
+    @observable _minimizedY = 0;
     @action
     onMinimizeMove = (e: PointerEvent): void => {
         e.stopPropagation();
@@ -201,12 +205,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             let xf = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
             let dx = e.pageX - xf[0];
             let dy = e.pageY - xf[1];
-            if (Math.abs(dx) < 20 && Math.abs(dy) < 20)
-                dx = dy = 0;
+            this._minimizedX = e.clientX;
+            this._minimizedY = e.clientY;
+            if (Math.abs(dx) < 20 && Math.abs(dy) < 20) {
+                this._minimizedX = xf[0];
+                this._minimizedY = xf[1];
+            }
             SelectionManager.SelectedDocuments().map(dv => {
-                let where = (dv.props.ScreenToLocalTransform()).scale(dv.props.ContentScaling()).transformDirection(dx, dy);
-                dv.props.Document.SetNumber(KeyStore.MinimizedX, where[0]);
-                dv.props.Document.SetNumber(KeyStore.MinimizedY, where[1]);
+                let minDoc = dv.props.Document.Get(KeyStore.MinimizedDoc);
+                if (minDoc instanceof Document) {
+                    let where = (dv.props.ScreenToLocalTransform()).scale(dv.props.ContentScaling()).transformPoint(this._minimizedX, this._minimizedY);
+                    let minDocument = minDoc as Document;
+                    minDocument.SetNumber(KeyStore.X, where[0] + dv.props.Document.GetNumber(KeyStore.X, 0));
+                    minDocument.SetNumber(KeyStore.Y, where[1] + dv.props.Document.GetNumber(KeyStore.Y, 0));
+                }
             });
         }
     }
@@ -219,6 +231,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (Math.abs(dx) < 4 && Math.abs(dy) < 4 && !this._iconifying) {
                 SelectionManager.SelectedDocuments().map(dv => dv.minimize());
                 SelectionManager.DeselectAll();
+            } else {
+                this._minimizedX = this._minimizedY = 0;
             }
             document.removeEventListener("pointermove", this.onMinimizeMove);
             document.removeEventListener("pointerup", this.onMinimizeUp);
@@ -404,24 +418,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (bounds.x === Number.MAX_VALUE || !seldoc) {
             return (null);
         }
-        let minvec = [seldoc.props.Document.GetNumber(KeyStore.MinimizedX, 0), seldoc.props.Document.GetNumber(KeyStore.MinimizedY, 0)];
-        minvec = seldoc.props.ScreenToLocalTransform().scale(seldoc.props.ContentScaling()).inverse().transformDirection(minvec[0], minvec[1]);
-        let selpos = minvec[0] !== 0 || minvec[1] !== 0 ?
-            [minvec[0] - 12 + (!this._iconifying ? 8 : 0), minvec[1] - 12 + (!this._iconifying ? 28 : 0)] :
+        let selpos = this._minimizedX !== 0 || this._minimizedY !== 0 ?
+            [this._minimizedX - 12 + (!this._iconifying ? 8 : 0), this._minimizedY - 12 + (!this._iconifying ? 28 : 0)] :
             [0, this._iconifying ? -18 : 0];
         let minimizeIcon = (
             <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}
                 style={{ transform: `translate(${selpos[0]}px,${selpos[1]}px)`, }}>
-                {SelectionManager.SelectedDocuments().length == 1 ? SelectionManager.SelectedDocuments()[0].minimizedIcon : "..."}
+                {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."}
             </div>);
         if (this._iconifying) {
-            let xfpt = seldoc.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-            return (<div className="documentDecorations-container" style={{ transform: `translate(${xfpt[0]}px,${xfpt[1]}px` }}>
-                {minimizeIcon}
-            </div>);
+            return (<div className="documentDecorations-container" > {minimizeIcon} </div>);
         }
-        // console.log(this._documents.length)
-        // let test = this._documents[0].props.Document.Title;
+
         if (this.Hidden) {
             return (null);
         }
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 503a11b35..09ef30f6b 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -85,11 +85,11 @@ export class Main extends React.Component {
         this.initEventListeners();
         this.initAuthenticationRouters();
 
-        // try {
-        //     this.initializeNorthstar();
-        // } catch (e) {
+        try {
+            this.initializeNorthstar();
+        } catch (e) {
 
-        // }
+        }
     }
 
     componentDidMount() { window.onpopstate = this.onHistory; }
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 6eabbe17c..cfdb3ab22 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,6 +1,7 @@
 @import "../globalCssVariables";
 
 
+
 .collectionSchemaView-container {
     border-width: $COLLECTION_BORDER_WIDTH;
     border-color : $intermediate-color;
@@ -10,6 +11,10 @@
     position: absolute;
     width: 100%;
     height: 100%;
+
+    .collectionSchemaView-cellContents {
+        height: $MAX_ROW_HEIGHT;
+    }
     
     .collectionSchemaView-previewRegion {
         position: relative;
@@ -104,7 +109,7 @@
         }
         .rt-tr-group {
             direction: ltr;
-            max-height: 44px;
+            max-height: $MAX_ROW_HEIGHT;
         }
         .rt-td {
             border-width: 1px;
@@ -136,7 +141,7 @@
     }
     .ReactTable .rt-th,
     .ReactTable .rt-td {
-        max-height: 44;
+        max-height: $MAX_ROW_HEIGHT;
         padding: 3px 7px;
         font-size: 13px;
         text-align: center;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b61eb342d..90077b053 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -5,6 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, untracked } from "mobx";
 import { observer } from "mobx-react";
 import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
+import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss'
 import "react-table/react-table.css";
 import { Document } from "../../../fields/Document";
 import { Field, Opt } from "../../../fields/Field";
@@ -99,11 +100,11 @@ export class CollectionSchemaView extends CollectionSubView {
             return false;
         };
         return (
-            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
+            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document.Id} ref={reference}>
                 <EditableView
                     display={"inline"}
                     contents={contents}
-                    height={56}
+                    height={Number(MAX_ROW_HEIGHT)}
                     GetValue={() => {
                         let field = props.Document.Get(props.fieldKey);
                         if (field && field instanceof Field) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 8868f7df0..20c5a84bf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -23,10 +23,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         let l = this.props.LinkDocs;
         let a = this.props.A;
         let b = this.props.B;
-        let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Width() / 2);
-        let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Height() / 2);
-        let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Width() / 2);
-        let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Height() / 2);
+        let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Width() / 2);
+        let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Height() / 2);
+        let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Width() / 2);
+        let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Height() / 2);
         return (
             <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
                 style={{ strokeWidth: `${l.length * 5}` }}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c50597655..945c01059 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -21,6 +21,7 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { BooleanField } from "../../../../fields/BooleanField";
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView {
@@ -71,17 +72,15 @@ export class CollectionFreeFormView extends CollectionSubView {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
-            console.log("DROP Aat " + de.x + " off " + de.data.xOffset);
             const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
             if (de.data.droppedDocuments.length) {
                 let dragDoc = de.data.droppedDocuments[0];
                 let dropX = dragDoc.GetNumber(KeyStore.X, 0);
                 let dropY = dragDoc.GetNumber(KeyStore.Y, 0);
                 de.data.droppedDocuments.map(d => {
-                    let minimized = d.GetBoolean(KeyStore.Minimized, false);
-                    d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - (minimized ? d.GetNumber(KeyStore.MinimizedX, 0) : 0)) - dropX);
-                    d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - (minimized ? d.GetNumber(KeyStore.MinimizedY, 0) : 0)) - dropY);
-                    if (!minimized) {
+                    d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0)) - dropX);
+                    d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0)) - dropY);
+                    if (!d.GetBoolean(KeyStore.IsMinimized, false)) {
                         if (!d.GetNumber(KeyStore.Width, 0)) {
                             d.SetNumber(KeyStore.Width, 300);
                         }
@@ -264,7 +263,9 @@ export class CollectionFreeFormView extends CollectionSubView {
         let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
             var page = doc.GetNumber(KeyStore.Page, -1);
             if (page === curPage || page === -1) {
-                prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+                let minim = doc.GetT(KeyStore.IsMinimized, BooleanField);
+                if (minim === undefined || (minim && !minim.Data))
+                    prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
             }
             return prev;
         }, [] as JSX.Element[]);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 8b94374fa..bf918beba 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -208,12 +208,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let selRect = this.Bounds;
         let selection: Document[] = [];
         this.props.activeDocuments().map(doc => {
-            let minimized = doc.GetBoolean(KeyStore.Minimized, false);
             var z = doc.GetNumber(KeyStore.Zoom, 1);
-            var x = doc.GetNumber(KeyStore.X, 0) + (minimized ? doc.GetNumber(KeyStore.MinimizedX, 0) : 0);
-            var y = doc.GetNumber(KeyStore.Y, 0) + (minimized ? doc.GetNumber(KeyStore.MinimizedY, 0) : 0);
-            var w = minimized ? MINIMIZED_ICON_SIZE : doc.Width() / z;
-            var h = minimized ? MINIMIZED_ICON_SIZE : doc.Height() / z;
+            var x = doc.GetNumber(KeyStore.X, 0);
+            var y = doc.GetNumber(KeyStore.Y, 0);
+            var w = doc.Width() / z;
+            var h = doc.Height() / z;
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index f154f8158..4f68b71b0 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -24,8 +24,10 @@ $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
 $COLLECTION_BORDER_WIDTH: 1;
 $MINIMIZED_ICON_SIZE:25;
+$MAX_ROW_HEIGHT: 44px;
 :export  {
     contextMenuZindex: $contextMenu-zindex;
     COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
     MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+    MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
 }
\ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index cc77d987a..9788d31f7 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -3,6 +3,7 @@ interface IGlobalScss {
     contextMenuZindex: string;  // context menu shows up over everything
     COLLECTION_BORDER_WIDTH: string;
     MINIMIZED_ICON_SIZE: string;
+    MAX_ROW_HEIGHT: string;
 }
 declare const globalCssVariables: IGlobalScss;
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a3689414d..1d42b3899 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -46,10 +46,10 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
     }
 
     get X() {
-        return this.props.Document.GetNumber(KeyStore.X, 0) + (this.isMinimized ? this.props.Document.GetNumber(KeyStore.MinimizedX, 0) : 0);
+        return this.props.Document.GetNumber(KeyStore.X, 0);
     }
     get Y() {
-        return this.props.Document.GetNumber(KeyStore.Y, 0) + (this.isMinimized ? this.props.Document.GetNumber(KeyStore.MinimizedY, 0) : 0);
+        return this.props.Document.GetNumber(KeyStore.Y, 0);
     }
     getTransform = (): Transform =>
         this.props.ScreenToLocalTransform()
@@ -57,8 +57,8 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
             .scale(1 / this.contentScaling()).scale(1 / this.zoom)
 
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
-    panelWidth = () => this.isMinimized ? 10 : this.props.PanelWidth();
-    panelHeight = () => this.isMinimized ? 10 : this.props.PanelHeight();
+    panelWidth = () => this.props.PanelWidth();
+    panelHeight = () => this.props.PanelHeight();
 
     @computed
     get docView() {
@@ -70,19 +70,17 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
         />;
     }
 
-    get isMinimized() { return this.props.Document.GetBoolean(KeyStore.Minimized, false); }
-
     render() {
         let zoomFade = 1;
         //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
-        let transform = this.getTransform().scale(this.contentScaling()).inverse();
-        var [sptX, sptY] = transform.transformPoint(0, 0);
-        let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
-        let w = bptX - sptX;
-        //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
-        let fadeUp = .75 * 1800;
-        let fadeDown = .075 * 1800;
-        zoomFade = w < fadeDown  /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+        // let transform = this.getTransform().scale(this.contentScaling()).inverse();
+        // var [sptX, sptY] = transform.transformPoint(0, 0);
+        // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+        // let w = bptX - sptX;
+        // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+        // let fadeUp = .75 * 1800;
+        // let fadeDown = .075 * 1800;
+        // zoomFade = w < fadeDown  /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 76f852601..07599c345 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -15,6 +15,7 @@ import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
+import { IconBox } from "./IconBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
@@ -62,7 +63,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             return <p>Error loading layout keys</p>;
         }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index a0e829d26..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -4,7 +4,7 @@
   position: inherit;
   top: 0;
   left:0;
-  background: $light-color; //overflow: hidden;
+ // background: $light-color; //overflow: hidden;
   transform-origin: left top;
 
   &.minimized {
@@ -30,18 +30,4 @@
 }
 .documentView-node-topmost {
     background: white;
-}
-.minimized-box {
-    position: absolute;
-    left:0;
-    top:0;
-    width:$MINIMIZED_ICON_SIZE;
-    height:$MINIMIZED_ICON_SIZE;
-    transform:translate(-50%, -50%);
-}
-
-.minimized-box:hover {
-  background: $main-accent;
-  transform:translate(-50%, -50%) scale(1.15);;
-  cursor: pointer;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3ada3252c..558cbf017 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,3 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faObjectGroup, faStickyNote, faFilePdf, faFilm, faImage } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
@@ -20,18 +17,11 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
-import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
 import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
+import { TextField } from "../../../fields/TextField";
 
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
@@ -189,10 +179,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         e.stopPropagation();
-        if (!SelectionManager.IsSelected(this) && e.button !== 2 &&
-            Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
-            SelectionManager.SelectDoc(this, e.ctrlKey);
-        }
+        if (!SelectionManager.IsSelected(this) && e.button !== 2)
+            if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
+                SelectionManager.SelectDoc(this, e.ctrlKey);
+            }
     }
     stopPropagation = (e: React.SyntheticEvent) => {
         e.stopPropagation();
@@ -221,13 +211,33 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
     }
 
+    @action createIcon = (layoutString: string): void => {
+        let iconDoc = Documents.IconDocument(layoutString);
+        iconDoc.SetBoolean(KeyStore.IsMinimized, false);
+        iconDoc.SetNumber(KeyStore.NativeWidth, 0);
+        iconDoc.SetNumber(KeyStore.NativeHeight, 0);
+        iconDoc.Set(KeyStore.Prototype, this.props.Document);
+        iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document);
+        this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc);
+        this.props.addDocument && this.props.addDocument(iconDoc, false);
+    }
+
     @action
-    public minimize = (where: number[]): void => {
-        this.props.Document.SetBoolean(KeyStore.Minimized, true);
-        if (where[0] !== 0 || where[1] !== 0)
-            this.props.Document.SetNumber(KeyStore.MinimizedX, where[0]);
-        if (where[1] !== 0 || where[0] !== 0)
-            this.props.Document.SetNumber(KeyStore.MinimizedY, where[1]);
+    public minimize = (): void => {
+        this.props.Document.SetBoolean(KeyStore.IsMinimized, true);
+        this.props.Document.GetAsync(KeyStore.MinimizedDoc, mindoc => {
+            if (mindoc === undefined) {
+                this.props.Document.GetAsync(KeyStore.BackgroundLayout, field => {
+                    if (field instanceof TextField) this.createIcon(field.Data);
+                    else this.props.Document.GetAsync(KeyStore.Layout, field => {
+                        if (field instanceof TextField) this.createIcon(field.Data);
+                    });
+                });
+            }
+            else if (mindoc instanceof Document) {
+                this.props.addDocument && this.props.addDocument(mindoc, false);
+            }
+        });
     }
 
     @undoBatch
@@ -295,7 +305,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         e.preventDefault();
 
-        !this.isMinimized() && ContextMenu.Instance.addItem({ description: "Minimize", event: () => this.minimize([0, 0]) });
         ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
         ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
         ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
@@ -309,9 +318,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             SelectionManager.SelectDoc(this, false);
     }
 
-    @action
-    expand = (e: React.MouseEvent) => { this.props.Document.SetBoolean(KeyStore.Minimized, false); SelectionManager.SelectDoc(this, e.ctrlKey); }
-    isMinimized = () => this.props.Document.GetBoolean(KeyStore.Minimized, false);
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
 
@@ -319,27 +325,12 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
     @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); }
 
-    @computed get minimizedIcon() {
-        let button = this.layout.indexOf("PDFBox") !== -1 ? faFilePdf :
-            this.layout.indexOf("ImageBox") !== -1 ? faImage :
-                this.layout.indexOf("Formatted") !== -1 ? faStickyNote :
-                    this.layout.indexOf("Video") !== -1 ? faFilm :
-                        this.layout.indexOf("Collection") !== -1 ? faObjectGroup :
-                            faCaretUp;
-        return <FontAwesomeIcon icon={button} style={{ width: MINIMIZED_ICON_SIZE, height: MINIMIZED_ICON_SIZE }} className="documentView-minimizedIcon" />
-    }
 
     render() {
         var scaling = this.props.ContentScaling();
         var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
         var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
 
-        if (this.isMinimized()) {
-            return (
-                <div className="minimized-box" ref={this._mainCont} onClick={this.expand} onDrop={this.onDrop} onPointerDown={this.onPointerDown} >
-                    {this.minimizedIcon}
-                </div>);
-        }
         return (
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index e9f6950ff..93e385821 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -23,6 +23,8 @@ import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../.
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { IconField } from "../../../fields/IconFIeld";
+import { IconBox } from "./IconBox";
 
 
 //
@@ -72,6 +74,9 @@ export class FieldView extends React.Component<FieldViewProps> {
         else if (field instanceof ImageField) {
             return <ImageBox {...this.props} />;
         }
+        else if (field instanceof IconField) {
+            return <IconBox {...this.props} />;
+        }
         else if (field instanceof VideoField) {
             return <VideoBox {...this.props} />;
         }
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
new file mode 100644
index 000000000..ce0ee2e09
--- /dev/null
+++ b/src/client/views/nodes/IconBox.scss
@@ -0,0 +1,12 @@
+
+@import "../globalCssVariables";
+.iconBox-container {
+    position: absolute;
+    left:0;
+    top:0;
+    svg {
+        width: 100% !important;
+        height: 100%;
+        background: white;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
new file mode 100644
index 000000000..5ada2186d
--- /dev/null
+++ b/src/client/views/nodes/IconBox.tsx
@@ -0,0 +1,90 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from '../../../fields/Document';
+import { IconField } from "../../../fields/IconFIeld";
+import { KeyStore } from "../../../fields/KeyStore";
+import { SelectionManager } from "../../util/SelectionManager";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./IconBox.scss";
+
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+@observer
+export class IconBox extends React.Component<FieldViewProps> {
+    public static LayoutString() { return FieldView.LayoutString(IconBox); }
+
+    @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); }
+    @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); }
+    @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
+
+    public static DocumentIcon(layout: string) {
+        let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+            layout.indexOf("ImageBox") !== -1 ? faImage :
+                layout.indexOf("Formatted") !== -1 ? faStickyNote :
+                    layout.indexOf("Video") !== -1 ? faFilm :
+                        layout.indexOf("Collection") !== -1 ? faObjectGroup :
+                            faCaretUp;
+        return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />
+    }
+
+    animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) {
+        setTimeout(() => {
+            let now = Date.now();
+            let progress = Math.min(1, (now - stime) / 200);
+            let pval = maximizing ?
+                [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
+                [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
+            target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress);
+            target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress);
+            target.SetNumber(KeyStore.X, pval[0]);
+            target.SetNumber(KeyStore.Y, pval[1]);
+            if (now < stime + 200) {
+                this.animateTransition(icon, targ, width, height, stime, target, maximizing);
+            }
+            else {
+                if (!maximizing) {
+                    target.SetBoolean(KeyStore.IsMinimized, true);
+                    target.SetNumber(KeyStore.X, targ[0]);
+                    target.SetNumber(KeyStore.Y, targ[1]);
+                    target.SetNumber(KeyStore.Width, width);
+                    target.SetNumber(KeyStore.Height, height);
+                }
+                this._completed = true;
+            }
+        },
+            2);
+    }
+
+    _completed = true;
+
+    @action
+    public toggleMinimize = (): void => {
+        SelectionManager.DeselectAll();
+        if (this.maximized instanceof Document && this._completed) {
+            this._completed = false;
+            let minimized = this.maximized.GetBoolean(KeyStore.IsMinimized, false);
+            this.maximized.SetBoolean(KeyStore.IsMinimized, false);
+            this.animateTransition(
+                [this.props.Document.GetNumber(KeyStore.X, 0), this.props.Document.GetNumber(KeyStore.Y, 0)],
+                [this.maximized.GetNumber(KeyStore.X, 0), this.maximized.GetNumber(KeyStore.Y, 0)],
+                this.maximized.GetNumber(KeyStore.Width, 0), this.maximized.GetNumber(KeyStore.Width, 0),
+                Date.now(), this.maximized, minimized);
+        }
+    }
+
+    render() {
+        return (
+            <div className="iconBox-container" onClick={this.toggleMinimize} >
+                {this.minimizedIcon}
+            </div>);
+    }
+}
\ No newline at end of file
diff --git a/src/fields/IconFIeld.ts b/src/fields/IconFIeld.ts
new file mode 100644
index 000000000..a6694cc49
--- /dev/null
+++ b/src/fields/IconFIeld.ts
@@ -0,0 +1,25 @@
+import { BasicField } from "./BasicField";
+import { FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class IconField extends BasicField<string> {
+    constructor(data: string = "", id?: FieldId, save: boolean = true) {
+        super(data, save, id);
+    }
+
+    ToScriptString(): string {
+        return `new IconField("${this.Data}")`;
+    }
+
+    Copy() {
+        return new IconField(this.Data);
+    }
+
+    ToJson() {
+        return {
+            type: Types.Icon,
+            data: this.Data,
+            id: this.Id
+        };
+    }
+}
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index ff2f31003..a347f8bcf 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -4,8 +4,6 @@ export namespace KeyStore {
     export const Prototype = new Key("Prototype");
     export const X = new Key("X");
     export const Y = new Key("Y");
-    export const MinimizedX = new Key("MinimizedX");
-    export const MinimizedY = new Key("MinimizedY");
     export const Page = new Key("Page");
     export const Title = new Key("Title");
     export const Author = new Key("Author");
@@ -47,14 +45,16 @@ export namespace KeyStore {
     export const OptionalRightCollection = new Key("OptionalRightCollection");
     export const Archives = new Key("Archives");
     export const Workspaces = new Key("Workspaces");
-    export const Minimized = new Key("Minimized");
+    export const IsMinimized = new Key("IsMinimized");
+    export const MinimizedDoc = new Key("MinimizedDoc");
+    export const MaximizedDoc = new Key("MaximizedDoc");
     export const CopyDraggedItems = new Key("CopyDraggedItems");
 
-    export const KeyList: Key[] = [Prototype, X, Y, MinimizedX, MinimizedY, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
+    export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
         Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
         LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
         LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
-        Archives, Workspaces, Minimized, CopyDraggedItems
+        Archives, Workspaces, IsMinimized, MinimizedDoc, MaximizedDoc, CopyDraggedItems
     ];
     export function KeyLookup(keyid: string) {
         for (const key of KeyList) {
diff --git a/src/server/Message.ts b/src/server/Message.ts
index bbe4ffcad..15916ef12 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -14,7 +14,7 @@ export class Message<T> {
 }
 
 export enum Types {
-    Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
+    Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
     Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
 }
 
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 818230c1a..79ca5e55d 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,6 +18,7 @@ import { NumberField } from "./../fields/NumberField";
 import { RichTextField } from "./../fields/RichTextField";
 import { TextField } from "./../fields/TextField";
 import { Transferable, Types } from "./Message";
+import { IconField } from "../fields/IconFIeld";
 
 export class ServerUtils {
     public static prepend(extension: string): string {
@@ -37,6 +38,7 @@ export class ServerUtils {
             case Types.Boolean: return new BooleanField(json.data, json.id, false);
             case Types.Number: return new NumberField(json.data, json.id, false);
             case Types.Text: return new TextField(json.data, json.id, false);
+            case Types.Icon: return new IconField(json.data, json.id, false);
             case Types.Html: return new HtmlField(json.data, json.id, false);
             case Types.Web: return new WebField(new URL(json.data), json.id, false);
             case Types.RichText: return new RichTextField(json.data, json.id, false);
-- 
cgit v1.2.3-70-g09d2


From 1eb965a5d9c8aaebf1970bc645edecfb7017b601 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 20 Apr 2019 03:29:35 -0400
Subject: Made the switch in a couple more classes

---
 src/Utils.ts                                    |  3 --
 src/client/views/nodes/DocumentContentsView.tsx | 26 ++------------
 src/client/views/nodes/DocumentView.tsx         | 16 +++++++--
 src/client/views/nodes/FieldView.tsx            | 45 ++++++++++---------------
 src/client/views/nodes/ImageBox.tsx             | 41 +++++++++++++---------
 src/debug/Test.tsx                              |  4 +--
 src/new_fields/Doc.ts                           |  5 +--
 src/new_fields/HtmlField.ts                     |  2 +-
 src/new_fields/Schema.ts                        | 24 +++++++++++--
 src/new_fields/Types.ts                         | 19 +++++++----
 10 files changed, 98 insertions(+), 87 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 98f75d3b9..066d653f5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,7 +2,6 @@ import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
 import { Message } from './server/Message';
-import { Document } from './fields/Document';
 
 export class Utils {
 
@@ -108,6 +107,4 @@ export function returnZero() { return 0; }
 
 export function emptyFunction() { }
 
-export function emptyDocFunction(doc: Document) { }
-
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 07599c345..273401111 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,9 +1,5 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { FieldWaiting, Field } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
@@ -22,46 +18,30 @@ import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
-import { Document } from "../../../fields/Document";
 import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
+import { Cast } from "../../../new_fields/Types";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
     props: BindingProps;
-    [keyName: string]: BindingProps | Field;
 }
 
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
-    layoutKey: Key
+    layoutKey: string
 }> {
-    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
-    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
-    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
+    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", "<p>Error loading layout data</p>"); }
 
     CreateBindings(): JsxBindings {
         let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
-
-        for (const key of this.layoutKeys) {
-            bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
-        }
-        for (const key of this.layoutFields) {
-            let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field !== FieldWaiting ? field.GetValue() : field;
-        }
         return bindings;
     }
 
     render() {
-        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
-        if (!lkeys || lkeys === FieldWaiting) {
-            return <p>Error loading layout keys</p>;
-        }
         return <JsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c06fe4c8d..f1a12e6db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -46,8 +46,17 @@ const schema = createSchema({
     backgroundColor: "string"
 });
 
-type Document = makeInterface<typeof schema>;
-const Document = makeInterface(schema);
+export const positionSchema = createSchema({
+    nativeWidth: "number",
+    nativeHeight: "number",
+    width: "number",
+    height: "number",
+    x: "number",
+    y: "number",
+});
+
+type Document = makeInterface<[typeof schema]>;
+const Document = makeInterface([schema]);
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
@@ -146,7 +155,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         e.stopPropagation();
-        if (!SelectionManager.IsSelected(this) && e.button !== 2)
+        if (!SelectionManager.IsSelected(this) && e.button !== 2) {
             if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
                 if (this.props.Document.Get(KeyStore.MaximizedDoc) instanceof Document) {
                     this.props.Document.GetAsync(KeyStore.MaximizedDoc, maxdoc => {
@@ -159,6 +168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     SelectionManager.SelectDoc(this, e.ctrlKey);
                 }
             }
+        }
     }
     stopPropagation = (e: React.SyntheticEvent) => {
         e.stopPropagation();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 93e385821..d8de5afec 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,30 +1,21 @@
 import React = require("react");
 import { observer } from "mobx-react";
 import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
-import { NumberField } from "../../../fields/NumberField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField";
-import { Key } from "../../../fields/Key";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
 import { WebBox } from "./WebBox";
 import { VideoBox } from "./VideoBox";
 import { AudioBox } from "./AudioBox";
-import { AudioField } from "../../../fields/AudioField";
-import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
-import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyFunction, returnOne } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { IconField } from "../../../fields/IconFIeld";
 import { IconBox } from "./IconBox";
+import { Opt, Doc, Field, FieldValue, FieldWaiting } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
 
 
 //
@@ -33,9 +24,9 @@ import { IconBox } from "./IconBox";
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc. 
 //
 export interface FieldViewProps {
-    fieldKey: Key;
+    fieldKey: string;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
-    Document: Document;
+    Document: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
@@ -56,17 +47,17 @@ export class FieldView extends React.Component<FieldViewProps> {
     }
 
     @computed
-    get field(): FieldValue<Field> {
-        const { Document: doc, fieldKey } = this.props;
-        return doc.Get(fieldKey);
+    get field(): FieldValue {
+        const { Document, fieldKey } = this.props;
+        return Document[fieldKey];
     }
     render() {
         const field = this.field;
         if (!field) {
             return <p>{'<null>'}</p>;
         }
-        if (field instanceof TextField) {
-            return <p>{field.Data}</p>;
+        if (typeof field === "string") {
+            return <p>{field}</p>;
         }
         else if (field instanceof RichTextField) {
             return <FormattedTextBox {...this.props} />;
@@ -83,7 +74,7 @@ export class FieldView extends React.Component<FieldViewProps> {
         else if (field instanceof AudioField) {
             return <AudioBox {...this.props} />;
         }
-        else if (field instanceof Document) {
+        else if (field instanceof Doc) {
             return (
                 <DocumentContentsView Document={field}
                     addDocument={undefined}
@@ -94,29 +85,29 @@ export class FieldView extends React.Component<FieldViewProps> {
                     PanelHeight={() => 100}
                     isTopMost={true} //TODO Why is this top most?
                     selectOnLoad={false}
-                    focus={emptyDocFunction}
+                    focus={emptyFunction}
                     isSelected={returnFalse}
                     select={returnFalse}
-                    layoutKey={KeyStore.Layout}
+                    layoutKey={"layout"}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
                     whenActiveChanged={this.props.whenActiveChanged} />
             );
         }
-        else if (field instanceof ListField) {
+        else if (field instanceof List) {
             return (<div>
-                {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
+                {field.map(f => f instanceof Doc ? f.title : f.toString()).join(", ")}
             </div>);
         }
         // bcz: this belongs here, but it doesn't render well so taking it out for now
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
-        else if (field instanceof NumberField) {
-            return <p>{field.Data}</p>;
+        else if (typeof field === "number") {
+            return <p>{field}</p>;
         }
         else if (field !== FieldWaiting) {
-            return <p>{JSON.stringify(field.GetValue())}</p>;
+            return <p>{JSON.stringify(field)}</p>;
         }
         else {
             return <p> {"Waiting for server..."} </p>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index fd5381b77..3b72e9f39 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -3,11 +3,6 @@ import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { FieldWaiting } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from '../../../fields/ListField';
 import { Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
@@ -15,9 +10,23 @@ import { ContextMenu } from "../../views/ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import { positionSchema } from './DocumentView';
+import { FieldValue, Cast } from '../../../new_fields/Types';
+import { URLField } from '../../../new_fields/URLField';
+import { Doc, FieldWaiting } from '../../../new_fields/Doc';
+import { List } from '../../../new_fields/List';
+
+const schema = createSchema({
+    curPage: "number"
+});
+
+type ImageDocument = makeInterface<[typeof schema, typeof positionSchema]>;
+const ImageDocument = makeInterface([schema, positionSchema]);
 
 @observer
-export class ImageBox extends React.Component<FieldViewProps> {
+export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
 
     public static LayoutString() { return FieldView.LayoutString(ImageBox); }
     private _imgRef: React.RefObject<HTMLImageElement>;
@@ -39,8 +48,8 @@ export class ImageBox extends React.Component<FieldViewProps> {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
         if (this._photoIndex === 0) {
-            this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
-            this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w);
+            this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+            this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
     }
 
@@ -126,9 +135,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     specificContextMenu = (e: React.MouseEvent): void => {
-        let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
+        let field = Cast(this.Document[this.props.fieldKey], URLField);
         if (field && field !== FieldWaiting) {
-            let url = field.Data.href;
+            let url = field.url.href;
             ContextMenu.Instance.addItem({
                 description: "Copy path", event: () => {
                     Utils.CopyText(url);
@@ -140,11 +149,11 @@ export class ImageBox extends React.Component<FieldViewProps> {
     @action
     onDotDown(index: number) {
         this._photoIndex = index;
-        this.props.Document.SetNumber(KeyStore.CurPage, index);
+        this.Document.curPage = index;
     }
 
     dots(paths: string[]) {
-        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
         let dist = Math.min(nativeWidth / paths.length, 40);
         let left = (nativeWidth - paths.length * dist) / 2;
         return paths.map((p, i) =>
@@ -155,12 +164,12 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey);
+        let field = this.Document[this.props.fieldKey];
         let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
         if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
-        else if (field instanceof ImageField) paths = [field.Data.href];
-        else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href);
-        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+        else if (field instanceof URLField) paths = [field.url.href];
+        else if (field instanceof List) paths = field.filter(val => val instanceof URLField).map(p => (p as URLField).url.href);
+        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
         return (
             <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 8b9c9fa0b..81c69ca8e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -16,8 +16,8 @@ const schema1 = createSchema({
     testDoc: Doc
 });
 
-const TestDoc = makeInterface(schema1);
-type TestDoc = makeInterface<typeof schema1>;
+const TestDoc = makeInterface([schema1]);
+type TestDoc = makeInterface<[typeof schema1]>;
 
 const schema2 = createSchema({
     hello: URLField,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 23a8c05cc..e0eb44ee9 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -33,6 +33,7 @@ export type Field = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting = null;
 export const FieldWaiting: FieldWaiting = null;
+export type FieldValue<T extends Field = Field> = Opt<T> | FieldWaiting;
 
 export const Self = Symbol("Self");
 
@@ -81,8 +82,8 @@ export namespace Doc {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
     }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Field | null | undefined {
-        return Cast(Get(doc, key, ignoreProto), ctor);
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): T | null | undefined {
+        return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
     }
     export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
         if (!doc) {
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index f8e54ade5..76fdb1f62 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -3,7 +3,7 @@ import { serializable, primitive } from "serializr";
 import { ObjectField } from "./Doc";
 
 @Deserializable("html")
-export class URLField extends ObjectField {
+export class HtmlField extends ObjectField {
     @serializable(primitive())
     readonly html: string;
 
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 1607d4c15..1d1f56844 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,8 +1,26 @@
-import { Interface, ToInterface, Cast, FieldCtor, ToConstructor } from "./Types";
+import { Interface, ToInterface, Cast, FieldCtor, ToConstructor, HasTail, Head, Tail } from "./Types";
 import { Doc } from "./Doc";
 
-export type makeInterface<T extends Interface, U extends Doc = Doc> = Partial<ToInterface<T>> & U;
-export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U> {
+type All<T extends any[], U extends Doc> = {
+    1: makeInterface<Head<T>, U> & All<Tail<T>, U>,
+    0: makeInterface<Head<T>, U>
+}[HasTail<T> extends true ? 1 : 0];
+
+type AllToInterface<T extends any[]> = {
+    1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
+    0: ToInterface<Head<T>>
+}[HasTail<T> extends true ? 1 : 0];
+
+export type makeInterface<T extends Interface[], U extends Doc = Doc> = Partial<AllToInterface<T>> & U;
+// export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
+// export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>; 
+export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U> {
+    let schema: Interface = {};
+    for (const s of schemas) {
+        for (const key in s) {
+            schema[key] = s[key];
+        }
+    }
     return function (doc: any) {
         return new Proxy(doc, {
             get(target, prop) {
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index cafb208ce..6ffb3e02f 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -27,14 +27,17 @@ export type Tail<T extends any[]> =
     ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
 export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
 
+//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
 export interface Interface {
     [key: string]: ToConstructor<Field> | ListSpec<Field>;
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-export type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
+export type FieldCtor<T extends Field> = T extends List<infer R> ? ListSpec<R> : ToConstructor<T>;
 
-export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined {
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined;
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>;
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T, defaultVal?: ToType<T>): ToType<T> | null | undefined {
     if (field !== undefined && field !== null) {
         if (typeof ctor === "string") {
             if (typeof field === ctor) {
@@ -42,17 +45,19 @@ export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined
             }
         } else if (typeof ctor === "object") {
             if (field instanceof List) {
-                return field as ToType<T>;
+                return field as any;
             }
         } else if (field instanceof (ctor as any)) {
             return field as ToType<T>;
         }
     } else {
-        return field;
+        return defaultVal;
     }
-    return undefined;
+    return defaultVal;
 }
 
-export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T> {
-    return field instanceof Promise ? undefined : field;
+export function FieldValue<T extends Field, U extends T>(field: Opt<T> | Promise<Opt<T>>, defaultValue: U): T;
+export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T>;
+export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>, defaultValue?: T): Opt<T> {
+    return field instanceof Promise ? defaultValue : field;
 }
-- 
cgit v1.2.3-70-g09d2


From 5ffb2f404a6a3da851c0fe7318b73f5c7723c3d7 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 23 Apr 2019 01:05:12 -0400
Subject: Did most of Documents and some other stuff

---
 src/Utils.ts                                       |  19 +-
 src/client/documents/Documents.ts                  | 202 +++++++++------------
 src/client/northstar/dash-fields/HistogramField.ts |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   2 +-
 src/new_fields/Doc.ts                              |  31 +++-
 src/new_fields/List.ts                             |   2 +-
 src/new_fields/Types.ts                            |   2 +-
 src/new_fields/util.ts                             |  12 +-
 9 files changed, 139 insertions(+), 135 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 066d653f5..59ff45dc9 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -86,13 +86,20 @@ export class Utils {
     }
 }
 
-export function OmitKeys(obj: any, keys: any, addKeyFunc?: (dup: any) => void) {
+export function OmitKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
+    const omit: any = { ...obj };
+    const extract: any = {};
+    keys.forEach(key => {
+        extract[key] = omit[key];
+        delete omit[key];
+    });
+    addKeyFunc && addKeyFunc(omit);
+    return { omit, extract };
+}
+
+export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
     var dup: any = {};
-    for (var key in obj) {
-        if (keys.indexOf(key) === -1) {
-            dup[key] = obj[key];
-        }
-    }
+    keys.forEach(key => dup[key] = obj[key]);
     addKeyFunc && addKeyFunc(dup);
     return dup;
 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b0bb74d89..a145a76c9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,20 +1,6 @@
-import { AudioField } from "../../fields/AudioField";
-import { Document } from "../../fields/Document";
-import { Field, Opt } from "../../fields/Field";
-import { HtmlField } from "../../fields/HtmlField";
-import { ImageField } from "../../fields/ImageField";
-import { InkField, StrokeData } from "../../fields/InkField";
-import { Key } from "../../fields/Key";
-import { KeyStore } from "../../fields/KeyStore";
-import { ListField } from "../../fields/ListField";
-import { PDFField } from "../../fields/PDFField";
-import { TextField } from "../../fields/TextField";
-import { VideoField } from "../../fields/VideoField";
-import { WebField } from "../../fields/WebField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
 import { HistogramOperation } from "../northstar/operations/HistogramOperation";
-import { Server } from "../Server";
 import { CollectionPDFView } from "../views/collections/CollectionPDFView";
 import { CollectionVideoView } from "../views/collections/CollectionVideoView";
 import { CollectionView } from "../views/collections/CollectionView";
@@ -34,39 +20,47 @@ import { AttributeTransformationModel } from "../northstar/core/attribute/Attrib
 import { AggregateFunction } from "../northstar/model/idea/idea";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { IconBox } from "../views/nodes/IconBox";
-import { IconField } from "../../fields/IconFIeld";
+import { Field, Doc, Opt } from "../../new_fields/Doc";
+import { OmitKeys } from "../../Utils";
+import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField";
+import { HtmlField } from "../../new_fields/HtmlField";
+import { List } from "../../new_fields/List";
+import { Cast } from "../../new_fields/Types";
 
 export interface DocumentOptions {
     x?: number;
     y?: number;
-    ink?: Map<string, StrokeData>;
+    // ink?: Map<string, StrokeData>;
     width?: number;
     height?: number;
     nativeWidth?: number;
     nativeHeight?: number;
     title?: string;
-    panx?: number;
-    pany?: number;
+    panX?: number;
+    panY?: number;
     page?: number;
     scale?: number;
     layout?: string;
-    layoutKeys?: Key[];
     viewType?: number;
     backgroundColor?: string;
     copyDraggedItems?: boolean;
+    backgroundLayout?: string;
+    curPage?: number;
+    // [key: string]: Opt<Field>;
 }
+const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
 
-export namespace Documents {
-    let textProto: Document;
-    let histoProto: Document;
-    let imageProto: Document;
-    let webProto: Document;
-    let collProto: Document;
-    let kvpProto: Document;
-    let videoProto: Document;
-    let audioProto: Document;
-    let pdfProto: Document;
-    let iconProto: Document;
+export namespace Docs {
+    let textProto: Doc;
+    let histoProto: Doc;
+    let imageProto: Doc;
+    let webProto: Doc;
+    let collProto: Doc;
+    let kvpProto: Doc;
+    let videoProto: Doc;
+    let audioProto: Doc;
+    let pdfProto: Doc;
+    let iconProto: Doc;
     const textProtoId = "textProto";
     const histoProtoId = "histoProto";
     const pdfProtoId = "pdfProto";
@@ -92,110 +86,80 @@ export namespace Documents {
             iconProto = fields[iconProtoId] as Document || CreateIconPrototype();
         });
     }
-    function assignOptions(doc: Document, options: DocumentOptions): Document {
-        if (options.nativeWidth !== undefined) { doc.SetNumber(KeyStore.NativeWidth, options.nativeWidth); }
-        if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); }
-        if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
-        if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
-        if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
-        if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
-        if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
-        if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
-        if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
-        if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
-        if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
-        if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
-        if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
-        return doc;
-    }
 
-    function assignToDelegate(doc: Document, options: DocumentOptions): Document {
-        if (options.x !== undefined) { doc.SetNumber(KeyStore.X, options.x); }
-        if (options.y !== undefined) { doc.SetNumber(KeyStore.Y, options.y); }
-        if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
-        if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
-        if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
-        if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
-        return doc;
+    function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
+        return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout });
     }
-
-    function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
-        return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
+    function SetInstanceOptions<U extends Field>(doc: Doc, options: DocumentOptions, value: U) {
+        const deleg = Doc.MakeDelegate(doc);
+        deleg.data = value;
+        return Doc.assign(deleg, options);
     }
-    function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
-        var deleg = doc.MakeDelegate(id);
-        if (value instanceof Document) {
-            deleg.Set(KeyStore.Data, value);
-        }
-        else {
-            deleg.SetData(KeyStore.Data, value[0], value[1]);
-        }
-        return assignOptions(deleg, options);
+    function SetDelegateOptions<U extends Field>(doc: Doc, options: DocumentOptions) {
+        const deleg = Doc.MakeDelegate(doc);
+        return Doc.assign(deleg, options);
     }
 
-    function CreateImagePrototype(): Document {
+    function CreateImagePrototype(): Doc {
         let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
-        imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
-        imageProto.SetNumber(KeyStore.CurPage, 0);
+            { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 });
         return imageProto;
     }
 
-    function CreateHistogramPrototype(): Document {
+    function CreateHistogramPrototype(): Doc {
         let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
-        histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
+            { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() });
         return histoProto;
     }
-    function CreateIconPrototype(): Document {
+    function CreateIconPrototype(): Doc {
         let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
-            { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
         return iconProto;
     }
-    function CreateTextPrototype(): Document {
+    function CreateTextPrototype(): Doc {
         let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 150 });
         return textProto;
     }
-    function CreatePdfPrototype(): Document {
+    function CreatePdfPrototype(): Doc {
         let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, nativeWidth: 1200, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
-        pdfProto.SetNumber(KeyStore.CurPage, 1);
-        pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString());
+            { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 });
         return pdfProto;
     }
-    function CreateWebPrototype(): Document {
+    function CreateWebPrototype(): Doc {
         let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 300 });
         return webProto;
     }
-    function CreateCollectionPrototype(): Document {
+    function CreateCollectionPrototype(): Doc {
         let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
-            { panx: 0, pany: 0, scale: 1, width: 500, height: 500, layoutKeys: [KeyStore.Data] });
+            { panX: 0, panY: 0, scale: 1, width: 500, height: 500 });
         return collProto;
     }
 
-    function CreateKVPPrototype(): Document {
+    function CreateKVPPrototype(): Doc {
         let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 150 });
         return kvpProto;
     }
-    function CreateVideoPrototype(): Document {
+    function CreateVideoPrototype(): Doc {
         let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
-        videoProto.SetNumber(KeyStore.CurPage, 0);
-        videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString());
+            { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 });
         return videoProto;
     }
-    function CreateAudioPrototype(): Document {
+    function CreateAudioPrototype(): Doc {
         let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 150 });
         return audioProto;
     }
 
+    function CreateInstance(proto: Doc, data: Field, options: DocumentOptions) {
+        const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
+        return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps);
+    }
 
     export function ImageDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(imageProto, options, [new URL(url), ImageField]).MakeDelegate(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+        return CreateInstance(imageProto, new ImageField(new URL(url)), options);
         // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
         //     [new URL(url), ImageField]);
         // doc.SetText(KeyStore.Caption, "my caption...");
@@ -204,23 +168,23 @@ export namespace Documents {
         // return doc;
     }
     export function VideoDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(videoProto, options, [new URL(url), VideoField]), options);
+        return CreateInstance(videoProto, new VideoField(new URL(url)), options);
     }
     export function AudioDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(audioProto, options, [new URL(url), AudioField]), options);
+        return CreateInstance(audioProto, new AudioField(new URL(url)), options);
     }
 
-    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string, delegId?: string) {
-        return assignToDelegate(SetInstanceOptions(histoProto, options, [histoOp, HistogramField], id).MakeDelegate(delegId), options);
+    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
+        return CreateInstance(histoProto, new HistogramField(histoOp), options);
     }
     export function TextDocument(options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options);
+        return CreateInstance(textProto, "", options);
     }
     export function IconDocument(icon: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(iconProto, { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data], layout: IconBox.LayoutString(), ...options }, [icon, IconField]), options);
+        return CreateInstance(iconProto, new IconField(icon), options);
     }
     export function PdfDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options);
+        return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
     }
     export async function DBDocument(url: string, options: DocumentOptions = {}) {
         let schemaName = options.title ? options.title : "-no schema-";
@@ -248,35 +212,35 @@ export namespace Documents {
         return Documents.TreeDocument([], { width: 50, height: 100, title: schemaName });
     }
     export function WebDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(webProto, options, [new URL(url), WebField]).MakeDelegate(), options);
+        return CreateInstance(webProto, new WebField(new URL(url)), options);
     }
     export function HtmlDocument(html: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(webProto, options, [html, HtmlField]).MakeDelegate(), options);
+        return CreateInstance(webProto, new HtmlField(html), options);
     }
-    export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
-        return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options);
+    export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
+        return CreateInstance(kvpProto, document, options);
     }
-    export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string, makePrototype: boolean = true) {
+    export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
         if (!makePrototype) {
-            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id);
+            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
         }
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options);
+        return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Freeform });
     }
-    export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options);
+    export function SchemaDocument(documents: Array<Doc>, options: DocumentOptions) {
+        return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Schema });
     }
-    export function TreeDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options);
+    export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
+        return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Tree });
     }
-    export function DockDocument(config: string, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options);
+    export function DockDocument(config: string, options: DocumentOptions) {
+        return CreateInstance(collProto, config, { ...options, viewType: CollectionViewType.Docking });
     }
 
-    export function CaptionDocument(doc: Document) {
-        const captionDoc = doc.CreateAlias();
-        captionDoc.SetText(KeyStore.OverlayLayout, FixedCaption());
-        captionDoc.SetNumber(KeyStore.Width, doc.GetNumber(KeyStore.Width, 0));
-        captionDoc.SetNumber(KeyStore.Height, doc.GetNumber(KeyStore.Height, 0));
+    export function CaptionDocument(doc: Doc) {
+        const captionDoc = Doc.MakeAlias(doc);
+        captionDoc.overlayLayout = FixedCaption();
+        captionDoc.width = Cast(doc.width, "number", 0);
+        captionDoc.height = Cast(doc.height, "number", 0);
         return captionDoc;
     }
 
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index c699691a4..932166b21 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -15,7 +15,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
     }
 
     toString(): string {
-        return JSON.stringify(OmitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']));
+        return JSON.stringify(OmitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit);
     }
 
     Copy(): Field {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c6eea5623..7b7b7e65e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -71,7 +71,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @computed
     get docView() {
-        return <DocumentView {...OmitKeys(this.props, ['zoomFade'])}
+        return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
             ContentScaling={this.contentScaling}
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 273401111..c90ca6d17 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -37,7 +37,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", "<p>Error loading layout data</p>"); }
 
     CreateBindings(): JsxBindings {
-        let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
+        let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
         return bindings;
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5ae095e68..987cb2cc4 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -4,7 +4,7 @@ import { autoObject, SerializationHelper, Deserializable } from "../client/util/
 import { Utils } from "../Utils";
 import { DocServer } from "../client/DocServer";
 import { setter, getter, getField } from "./util";
-import { Cast, ToConstructor } from "./Types";
+import { Cast, ToConstructor, PromiseValue } from "./Types";
 
 export type FieldId = string;
 export const HandleUpdate = Symbol("HandleUpdate");
@@ -54,7 +54,8 @@ export class Doc extends RefField {
         return doc;
     }
 
-    [key: string]: Field | FieldWaiting | undefined;
+    proto: FieldResult<Doc>;
+    [key: string]: FieldResult;
 
     @serializable(alias("fields", map(autoObject())))
     @observable
@@ -92,6 +93,32 @@ export namespace Doc {
             proto[key] = value;
         }
     }
+    export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>) {
+        for (const key in fields) {
+            if (fields.hasOwnProperty(key)) {
+                const value = fields[key];
+                if (value !== undefined) {
+                    doc[key] = value;
+                }
+            }
+        }
+        return doc;
+    }
+
+    export function MakeAlias(doc: Doc) {
+        const alias = new Doc;
+
+        PromiseValue(Cast(doc.proto, Doc)).then(proto => {
+            if (proto) {
+                alias.proto = proto;
+            }
+        });
+
+        return alias;
+    }
+
+    export function MakeDelegate(doc: Doc): Doc;
+    export function MakeDelegate(doc: Opt<Doc>): Opt<Doc>;
     export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
         if (!doc) {
             return undefined;
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index f3ec9e2c5..f01ac210a 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -33,4 +33,4 @@ class ListImpl<T extends Field> extends ObjectField {
     private [Self] = this;
 }
 export type List<T extends Field> = ListImpl<T> & T[];
-export const List: { new <T extends Field>(): List<T> } = ListImpl as any;
\ No newline at end of file
+export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
\ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 246b0624e..55083765a 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -68,7 +68,7 @@ export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>, def
 }
 
 export interface PromiseLike<T> {
-    then(callback: (field: Opt<T> | PromiseLike<T>) => void): void;
+    then(callback: (field: Opt<T>) => void): void;
 }
 export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
     return field instanceof Promise ? field : { then(cb: ((field: Opt<T>) => void)) { return cb(field); } };
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 3806044bd..2d9721b2e 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -2,6 +2,7 @@ import { UndoManager } from "../client/util/UndoManager";
 import { Update, OnUpdate, Parent, ObjectField, RefField, Doc, Id, Field } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField } from "./Proxy";
+import { FieldValue } from "./Types";
 
 export function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
     if (SerializationHelper.IsSerializing()) {
@@ -61,11 +62,16 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea
         return field.value(callback);
     }
     if (field === undefined && !ignoreProto) {
-        const proto = getField(target, "prototype", true);
+        const proto = getField(target, "proto", true);
         if (proto instanceof Doc) {
             let field = proto[prop];
-            callback && callback(field === null ? undefined : field);
-            return field;
+            if (field instanceof Promise) {
+                callback && field.then(callback);
+                return undefined;
+            } else {
+                callback && callback(field);
+                return field;
+            }
         }
     }
     callback && callback(field);
-- 
cgit v1.2.3-70-g09d2


From a98672afa22eeb5f4c24edd3207c1b043cd012e7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 29 Apr 2019 01:01:59 -0400
Subject: fixed templates

---
 src/client/views/MainOverlayTextBox.tsx         |  8 +++-----
 src/client/views/Templates.tsx                  |  6 +++---
 src/client/views/nodes/DocumentContentsView.tsx |  4 +++-
 src/client/views/nodes/DocumentView.tsx         | 23 +++++++++++++----------
 src/client/views/nodes/FormattedTextBox.tsx     | 12 ++++++------
 5 files changed, 28 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index be8d67925..209b1202c 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -89,12 +89,10 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
 
     render() {
         if (this.TextDoc) {
-            let toScreenXf = this._textXf().inverse();
-            let pt = toScreenXf.transformPoint(0, 0);
-            let s = 1 / this._textXf().Scale;
-            return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${pt[0]}px, ${pt[1]}px) scale(${s},${s})`, width: "auto", height: "auto" }} >
+            let s = this._textXf().Scale;
+            return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${this._textRect.x}px, ${this._textRect.y}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
-                    style={{ width: `${this.TextDoc.Width()}px`, height: `${this.TextDoc.Height()}px` }}>
+                    style={{ width: `${this._textRect.width * s}px`, height: `${this._textRect.height * s}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                         ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyDocFunction} />
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 52021861f..a0f9fe7e5 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -39,15 +39,15 @@ export namespace Templates {
     // export const BasicLayout = new Template("Basic layout", "{layout}");
 
     export const OuterCaption = new Template("Outer caption", TemplatePosition.OutterBottom,
-        `<div><div style="margin:auto; height:calc(100%); width:100%;">{layout}</div><div style="height:(100% + 50px); width:100%; position:absolute"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div></div>`
+        `<div><div style="margin:auto; height:calc(100%); width:100%;">{layout}</div><div style="height:(100% + 50px); width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={CaptionKey} /></div></div>`
     );
 
     export const InnerCaption = new Template("Inner caption", TemplatePosition.InnerBottom,
-        `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div></div>`
+        `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={CaptionKey}/></div></div>`
     );
 
     export const SideCaption = new Template("Side caption", TemplatePosition.OutterRight,
-        `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div> </div>`
+        `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox {...props} fieldKey={CaptionKey}/></div> </div>`
     );
 
     export const Title = new Template("Title", TemplatePosition.InnerTop,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 07599c345..e9c46aa9d 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -47,7 +47,9 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     CreateBindings(): JsxBindings {
         let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
 
-        for (const key of this.layoutKeys) {
+        let keys: Key[] = [];
+        keys.push(...this.layoutKeys, KeyStore.Caption) // bcz: hack to get templates to work
+        for (const key of keys) {
             bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
         }
         for (const key of this.layoutFields) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bae14193e..6dddab600 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -26,6 +26,7 @@ import React = require("react");
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
+import { TextField } from "../../../fields/TextField";
 
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
@@ -88,7 +89,6 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
 export class DocumentView extends React.Component<DocumentViewProps> {
     private _downX: number = 0;
     private _downY: number = 0;
-    @computed get base(): string { return this.props.Document.GetText(KeyStore.BaseLayout, "<p>Error loading base layout data</p>"); }
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
 
@@ -249,17 +249,20 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
     }
 
-    updateLayout = (): void => {
-        let base = this.base;
-        let layout = this.base;
+    updateLayout = async () => {
+        const baseLayout = await this.props.Document.GetTAsync(KeyStore.BaseLayout, TextField);
+        if (baseLayout) {
+            let base = baseLayout.Data;
+            let layout = baseLayout.Data;
 
-        this.templates.forEach(template => {
-            let temp = template.Layout;
-            layout = temp.replace("{layout}", base);
-            base = layout;
-        });
+            this.templates.forEach(template => {
+                let temp = template.Layout;
+                layout = temp.replace("{layout}", base);
+                base = layout;
+            });
 
-        this.props.Document.SetText(KeyStore.Layout, layout);
+            this.props.Document.SetText(KeyStore.Layout, layout);
+        }
     }
 
     @action
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index fc8d757f6..92957ed19 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -50,6 +50,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
     private _ref: React.RefObject<HTMLDivElement>;
+    private _proseRef: React.RefObject<HTMLDivElement>;
     private _editorView: Opt<EditorView>;
     private _gotDown: boolean = false;
     private _reactionDisposer: Opt<IReactionDisposer>;
@@ -60,6 +61,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         super(props);
 
         this._ref = React.createRef();
+        this._proseRef = React.createRef();
         this.onChange = this.onChange.bind(this);
     }
 
@@ -188,9 +190,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
 
     onFocused = (e: React.FocusEvent): void => {
         if (!this.props.isOverlay) {
-            if (MainOverlayTextBox.Instance.TextDoc != this.props.Document) {
-                MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
-            }
+            MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
         } else {
             if (this._ref.current) {
                 this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
@@ -232,7 +232,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
     }
 
     onClick = (e: React.MouseEvent): void => {
-        this._ref.current!.focus();
+        this._proseRef.current!.focus();
     }
 
     tooltipTextMenuPlugin() {
@@ -277,7 +277,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         let color = this.props.Document.GetText(KeyStore.BackgroundColor, "");
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
         return (
-            <div className={`formattedTextBox-cont${style}`}
+            <div className={`formattedTextBox-cont${style}`} ref={this._ref}
                 style={{
                     pointerEvents: interactive ? "all" : "none",
                     background: color,
@@ -292,7 +292,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
                 // tfs: do we need this event handler
                 onWheel={this.onPointerWheel}
             >
-                <div className={`formattedTextBox-inner${rounded}`} ref={this._ref} />
+                <div className={`formattedTextBox-inner${rounded}`} ref={this._proseRef} />
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 407c104f0ad0957d71f73c14f5b835fab387ecd2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 2 May 2019 00:50:08 -0400
Subject: fixed up templates a bit. starting on summarization

---
 src/client/views/Templates.tsx                     |  3 ++
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 +++-
 src/client/views/nodes/DocumentContentsView.tsx    | 25 +++++++++++++++--
 src/client/views/nodes/DocumentView.tsx            | 32 ++++------------------
 4 files changed, 36 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 25d89772e..e17dd2354 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -53,6 +53,9 @@ export namespace Templates {
     export const Title = new Template("Title", TemplatePosition.InnerTop,
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
     );
+    export const Summary = new Template("Title", TemplatePosition.InnerTop,
+        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.doc1.title}</div></div>`
+    );
 
     export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption];
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 85a12defa..c58e7780c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -13,6 +13,8 @@ import { Utils } from "../../../../Utils";
 import { Doc } from "../../../../new_fields/Doc";
 import { NumCast, Cast } from "../../../../new_fields/Types";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { Templates } from "../../Templates";
+import { List } from "../../../../new_fields/List";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -178,7 +180,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             // SelectionManager.DeselectAll();
             if (e.key === "r") {
                 let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-                Doc.MakeLink(summary.proto!, newCollection.proto!);
+                summary.doc1 = newCollection.proto!;
+                summary.templates = new List<string>([Templates.Summary.Layout]);
                 this.props.addLiveTextDocument(summary);
                 e.preventDefault();
             }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 794442469..24e8a36ae 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -20,7 +20,8 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
 import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -40,11 +41,31 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
     }
 
+    @computed get templates(): List<string> {
+        let field = this.props.Document.templates;
+        if (field && field instanceof List) {
+            return field;
+        }
+        return new List<string>();
+    }
+    set templates(templates: List<string>) { this.props.Document.templates = templates; }
+    get finalLayout() {
+        const baseLayout = this.layout;
+        let base = baseLayout;
+        let layout = baseLayout;
+
+        this.templates.forEach(template => {
+            layout = template.replace("{layout}", base);
+            base = layout;
+        });
+        return layout;
+    }
+
     render() {
         return <JsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
-            jsx={this.layout}
+            jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
         />;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ae2d3af13..e83f3fcba 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -243,42 +243,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    updateLayout = async () => {
-        const baseLayout = await StrCast(this.props.Document.baseLayout);
-        if (baseLayout) {
-            let base = baseLayout;
-            let layout = baseLayout;
-
-            this.templates.forEach(template => {
-                layout = template.replace("{layout}", base);
-                base = layout;
-            });
-
-            this.props.Document.layout = layout;
-        }
-    }
 
     @action
     addTemplate = (template: Template) => {
-        let templates = this.templates;
-        templates.push(template.Layout);
-        this.templates = new List<string>(templates.map(t => t));
-        this.updateLayout();
+        this.templates.push(template.Layout);
+        this.templates = this.templates;
     }
 
     @action
     removeTemplate = (template: Template) => {
-        let templates = this.templates;
-        for (let i = 0; i < templates.length; i++) {
-            let temp = templates[i];
-            if (temp === template.Layout) {
-                templates.splice(i, 1);
+        for (let i = 0; i < this.templates.length; i++) {
+            if (this.templates[i] === template.Layout) {
+                this.templates.splice(i, 1);
                 break;
             }
         }
-        templates = new List<string>(templates.splice(0, templates.length));
-        this.templates = templates;
-        this.updateLayout();
+        this.templates = this.templates;
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From a78282cdf7fbb99386484640e1fb89d4b9b0cbee Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 2 May 2019 16:19:07 -0400
Subject: fixed some things with trees and summaries.

---
 src/client/views/Main.tsx                                    |  3 +--
 src/client/views/Templates.tsx                               | 12 +++++++++++-
 src/client/views/collections/CollectionTreeView.tsx          | 10 +++++++---
 .../views/collections/collectionFreeForm/MarqueeView.tsx     |  4 +++-
 src/client/views/nodes/DocumentContentsView.tsx              |  3 ++-
 src/client/views/nodes/FieldView.tsx                         | 11 ++++-------
 src/client/views/nodes/ImageBox.scss                         |  2 ++
 src/new_fields/List.ts                                       |  6 +++---
 src/server/authentication/models/current_user_utils.ts       |  2 ++
 9 files changed, 35 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index a07a2d5b1..97eb73d7f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -144,8 +144,7 @@ export class Main extends React.Component {
     createNewWorkspace = async (id?: string) => {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
-            let libraryDoc = Docs.TreeDocument([CurrentUserUtils.UserDocument], { x: 0, y: 400, title: `Library: ${CurrentUserUtils.email} ${list.length + 1}` });
-            libraryDoc.excludeFromLibrary = true;
+            let libraryDoc = await (CurrentUserUtils.UserDocument.library as Doc);
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
             var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(libraryDoc, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
             let mainDoc = Docs.DockDocument([libraryDoc, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index e17dd2354..668ae5312 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -54,7 +54,17 @@ export namespace Templates {
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
     );
     export const Summary = new Template("Title", TemplatePosition.InnerTop,
-        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.doc1.title}</div></div>`
+        `<div style="height:100%; width:100%;position:absolute; margin:0; padding: 0">
+            <div style="height:60%; width:100%;position:absolute;background:yellow; padding:0; margin:0">
+                {layout}
+            </div>
+            <div style="bottom:0px; height:40%; width:50%; position:absolute; background-color: rgba(0, 0, 0, .4); color: white;">
+                <FieldView {...props} fieldKey={"doc1"} />
+            </div>
+            <div style="bottom:0; left: 50%; height:40%; width:50%; position:absolute; background-color: rgba(0, 0, 0, .4); color: white;">
+                <FieldView {...props} fieldKey={"doc2"} />
+            </div>
+        </div>`
     );
 
     export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption];
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 48b226615..2cef1462b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
@@ -145,8 +145,11 @@ class TreeView extends React.Component<TreeViewProps> {
         </div>;
     }
     public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
-        return docs.filter(child => !child.excludeFromLibrary).map(child =>
-            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child => {
+            console.log("child = " + child[Id]);
+            return <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />
+        }
+        );
     }
 }
 
@@ -168,6 +171,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     render() {
+        trace();
         const children = this.children;
         let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
         if (!children) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c58e7780c..82027a6f2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -180,7 +180,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             // SelectionManager.DeselectAll();
             if (e.key === "r") {
                 let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-                summary.doc1 = newCollection.proto!;
+                summary.doc1 = selected[0];
+                if (selected.length > 1)
+                    summary.doc2 = selected[1];
                 summary.templates = new List<string>([Templates.Summary.Layout]);
                 this.props.addLiveTextDocument(summary);
                 e.preventDefault();
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 24e8a36ae..ddfe79a5c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -15,6 +15,7 @@ import { IconBox } from "./IconBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
+import { FieldView } from "./FieldView";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
@@ -63,7 +64,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
             jsx={this.finalLayout}
             showWarnings={true}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 63009f2db..a1e083b36 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -57,12 +57,12 @@ export class FieldView extends React.Component<FieldViewProps> {
     }
     render() {
         const field = this.field;
-        if (!field) {
+        if (field === undefined) {
             return <p>{'<null>'}</p>;
         }
-        if (typeof field === "string") {
-            return <p>{field}</p>;
-        }
+        // if (typeof field === "string") {
+        //     return <p>{field}</p>;
+        // }
         else if (field instanceof RichTextField) {
             return <FormattedTextBox {...this.props} />;
         }
@@ -108,9 +108,6 @@ export class FieldView extends React.Component<FieldViewProps> {
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
-        else if (typeof field === "number") {
-            return <p>{field}</p>;
-        }
         else if (!(field instanceof Promise)) {
             return <p>{JSON.stringify(field)}</p>;
         }
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9fe211df0..2316a050e 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -10,6 +10,8 @@
 }
 .imageBox-cont-interactive {
   pointer-events: all;
+  width:100%;
+  height:auto;
 }
 
 .imageBox-dot {
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 1c4b96c81..db7932cec 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -2,7 +2,7 @@ import { Deserializable, autoObject } from "../client/util/SerializationHelper";
 import { Field, Update, Self, FieldResult } from "./Doc";
 import { setter, getter, deleteProperty } from "./util";
 import { serializable, alias, list } from "serializr";
-import { observable } from "mobx";
+import { observable, action } from "mobx";
 import { ObjectField, OnUpdate, Copy } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
@@ -25,12 +25,12 @@ const listHandlers: any = {
         this[Update]();
         return field;
     },
-    push(...items: any[]) {
+    push: action(function (this: any, ...items: any[]) {
         items = items.map(toObjectField);
         const res = this[Self].__fields.push(...items);
         this[Update]();
         return res;
-    },
+    }),
     reverse() {
         const res = this[Self].__fields.reverse();
         this[Update]();
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9db470ca0..93c2afb1d 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -26,6 +26,8 @@ export class CurrentUserUtils {
         doc.title = this.email;
         doc.data = new List<Doc>();
         doc.optionalRightCollection = Docs.SchemaDocument([], { title: "Pending documents" });
+        doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+        (doc.library as Doc).excludeFromLibrary = true;
         return doc;
     }
 
-- 
cgit v1.2.3-70-g09d2


From 14232f5951af02d5a10ce0435a8b38a4e0502cbb Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 2 May 2019 23:13:07 -0400
Subject: Made DocumentContents JsxParser an observer

---
 src/client/views/nodes/DocumentContentsView.tsx | 11 ++++++++++-
 src/new_fields/Doc.ts                           |  1 -
 2 files changed, 10 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ddfe79a5c..bbc927b5a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -30,6 +30,15 @@ export interface JsxBindings {
     props: BindingProps;
 }
 
+class ObserverJsxParser1 extends JsxParser {
+    constructor(props: any) {
+        super(props);
+        observer(this as any);
+    }
+}
+
+const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
+
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
@@ -63,7 +72,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
-        return <JsxParser
+        return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
             jsx={this.finalLayout}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 978aacaac..3eea6d2b2 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -10,7 +10,6 @@ import { List } from "./List";
 import { ObjectField, Parent, OnUpdate } from "./ObjectField";
 import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
 import { Docs } from "../client/documents/Documents";
-// import { Docs } from "../client/documents/Documents";
 
 export function IsField(field: any): field is Field {
     return (typeof field === "string")
-- 
cgit v1.2.3-70-g09d2


From d588a4543f1ccc06d1e45fe748007b730c18c042 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 8 May 2019 00:45:14 -0400
Subject: small fixes to titles, templates, minimizing, and  keyvalue deletion

---
 src/client/views/Templates.tsx                             | 12 ++++++++++--
 .../views/collections/collectionFreeForm/MarqueeView.tsx   | 13 +++++++++++--
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx  |  6 +++---
 src/client/views/nodes/DocumentContentsView.tsx            | 14 ++++++++++----
 src/client/views/nodes/KeyValuePair.tsx                    |  5 +++--
 5 files changed, 37 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 51fca4c41..de94a1578 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -50,6 +50,10 @@ export namespace Templates {
         `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox {...props} fieldKey={"caption"}/></div> </div>`
     );
 
+    export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
+        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
+        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
+    );
     export const Title = new Template("Title", TemplatePosition.InnerTop,
         `<div><div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div>
         <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
@@ -57,10 +61,14 @@ export namespace Templates {
 
     export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
-        <div className="isBullet" style="height:25px; width:25px; margin-left:-25px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px"/></div>`
+        <div id="isBullet" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
+        <img  id="isBullet"  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg==" 
+         width="15px" height="15px"/>
+         </div>
+        </div>`
     );
 
-    export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption, Bullet];
+    export const TemplateList: Template[] = [Title, TitleOverlay, OuterCaption, InnerCaption, SideCaption, Bullet];
 
     export function sortTemplates(a: Template, b: Template) {
         if (a.Position < b.Position) { return -1; }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c9b0b28f7..b9a6792ed 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -63,8 +63,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 let text = await navigator.clipboard.readText();
                 let ns = text.split("\n").filter(t => t != "\r");
                 for (let i = 0; i < ns.length - 1; i++) {
-                    while (!(ns[i].endsWith("-\r") || ns[i].endsWith(".\r") || ns[i].endsWith(":\r")) && i < ns.length - 1) {
-                        ns.splice(i, 2, ns[i].substr(0, ns[i].length - 1) + ns[i + 1].trimLeft());
+                    if (ns[i].trim() === "") {
+                        ns.splice(i, 1);
+                        continue;
+                    }
+                    while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
+                        ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
+                        ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
+                        let sub = ns[i].endsWith("\r") ? 1 : 0;
+                        let br = ns[i + 1].trim() === "";
+                        ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
+                        if (br) break;
                     }
                 }
                 ns.map(line => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index df78d92e2..0e34cb626 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -145,8 +145,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     if (isMinimized === undefined) {
                         isMinimized = BoolCast(maximizedDoc.isMinimized, false);
                     }
-                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) * this.getTransform().Scale / 2;
-                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) * this.getTransform().Scale / 2;
+                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) * this.getTransform().Scale * this.contentScaling() / 2;
+                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) * this.getTransform().Scale * this.contentScaling() / 2;
                     let maxx = NumCast(maximizedDoc.x, undefined);
                     let maxy = NumCast(maximizedDoc.y, undefined);
                     let maxw = NumCast(maximizedDoc.width, undefined);
@@ -176,7 +176,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let altKey = e.altKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            if (BoolCast(this.props.Document.isButton, false) || (e.target as any).className === "isBullet") {
+            if (BoolCast(this.props.Document.isButton, false) || (e.target as any).id === "isBullet") {
                 let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index bbc927b5a..1dc6343e8 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -64,10 +64,16 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         let base = baseLayout;
         let layout = baseLayout;
 
-        this.templates.forEach(template => {
-            layout = template.replace("{layout}", base);
-            base = layout;
-        });
+        // bcz: templates are intended for the main document layout.  However, 
+        // a DocumentContentsView is also used to render the annotation overlay for a document.
+        // So we detect that here by checking the layoutKey.  This should probably be moved into
+        // a prop so that the overlay can explicitly turn off templates.
+        if (this.props.layoutKey !== "backgroundLayout") {
+            this.templates.forEach(template => {
+                layout = template.replace("{layout}", base);
+                base = layout;
+            });
+        }
         return layout;
     }
 
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 203fb5625..5de660d57 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,8 +46,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                 <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
                         <button className="keyValuePair-td-key-delete" onClick={() => {
-                            let field = FieldValue(props.Document[props.fieldKey]);
-                            field && (props.Document[props.fieldKey] = undefined);
+                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1)
+                                props.Document[props.fieldKey] = undefined;
+                            else props.Document.proto![props.fieldKey] = undefined;
                         }}>
                             X
                         </button>
-- 
cgit v1.2.3-70-g09d2


From a8a5cf33985c47cb6e7c68f30c482232fc8d023a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 8 May 2019 10:13:13 -0400
Subject: converted template constants to screen space - need to think about
 more flexible api

---
 src/client/views/Templates.tsx                  |  8 ++++++--
 src/client/views/nodes/DocumentContentsView.tsx | 14 ++++++++++----
 2 files changed, 16 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index de94a1578..6f706bdc5 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -52,11 +52,15 @@ export namespace Templates {
 
     export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
         `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
-        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
+        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+        <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+        </div></div>`
     );
     export const Title = new Template("Title", TemplatePosition.InnerTop,
         `<div><div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div>
-        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
+        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+            <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+        </div></div>`
     );
 
     export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 1dc6343e8..294e17720 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -64,13 +64,19 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         let base = baseLayout;
         let layout = baseLayout;
 
-        // bcz: templates are intended for the main document layout.  However, 
-        // a DocumentContentsView is also used to render the annotation overlay for a document.
-        // So we detect that here by checking the layoutKey.  This should probably be moved into
+        // bcz: templates are intended only for a document's primary layout (not background).  However, 
+        // a DocumentContentsView is used to render  annotation overlays, so we detect that here 
+        // by checking the layoutKey.  This should probably be moved into
         // a prop so that the overlay can explicitly turn off templates.
         if (this.props.layoutKey !== "backgroundLayout") {
             this.templates.forEach(template => {
-                layout = template.replace("{layout}", base);
+                let self = this;
+                function convertConstantsToNative(match: string, offset: number, x: string) {
+                    let px = Number(match.replace("px", ""));
+                    return `${px * self.props.ScreenToLocalTransform().Scale}px`;
+                }
+                let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
+                layout = nativizedTemplate.replace("{layout}", base);
                 base = layout;
             });
         }
-- 
cgit v1.2.3-70-g09d2


From c67fc234cf4a524c19989134d878797563207e93 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 8 May 2019 16:05:02 -0400
Subject: fixes for templates and for dragging

---
 src/client/util/DragManager.ts                           | 16 +++++++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx        | 12 ++++--------
 .../views/nodes/CollectionFreeFormDocumentView.tsx       |  2 +-
 src/client/views/nodes/DocumentContentsView.tsx          | 13 +++++++------
 src/client/views/nodes/FormattedTextBox.tsx              |  2 +-
 5 files changed, 22 insertions(+), 23 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a3dbe6e43..ec192eaff 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -3,7 +3,7 @@ import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
 import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCast } from "../../new_fields/Doc";
 import { Cast } from "../../new_fields/Types";
 import { listSpec } from "../../new_fields/Schema";
 
@@ -42,12 +42,14 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
 
 export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
     let srcTarg = sourceDoc.proto;
-    let draggedDocs = srcTarg ?
-        Cast(srcTarg.linkedToDocs, listSpec(Doc), []).map(linkDoc =>
-            Cast(linkDoc.linkedTo, Doc) as Doc) : [];
-    let draggedFromDocs = srcTarg ?
-        Cast(srcTarg.linkedFromDocs, listSpec(Doc), []).map(linkDoc =>
-            Cast(linkDoc.linkedFrom, Doc) as Doc) : [];
+    let draggedDocs: Doc[] = [];
+    let draggedFromDocs: Doc[] = []
+    if (srcTarg) {
+        let linkToDocs = await DocListCast(srcTarg.linkedToDocs);
+        let linkFromDocs = await DocListCast(srcTarg.linkedFromDocs);
+        if (linkToDocs) draggedDocs = linkToDocs.map(linkDoc => Cast(linkDoc.linkedTo, Doc) as Doc);
+        if (linkFromDocs) draggedFromDocs = linkFromDocs.map(linkDoc => Cast(linkDoc.linkedFrom, Doc) as Doc);
+    }
     draggedDocs.push(...draggedFromDocs);
     if (draggedDocs.length) {
         let moddrag: Doc[] = [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7fa945891..fcc73d5b6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -326,10 +326,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 @observer
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {
     @computed get overlayView() {
-        let overlayLayout = Cast(this.props.Document.overlayLayout, "string", "");
-        return !overlayLayout ? (null) :
-            (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-                isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+        return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+            isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
     }
     render() {
         return this.overlayView;
@@ -339,10 +337,8 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get backgroundView() {
-        let backgroundLayout = Cast(this.props.Document.backgroundLayout, "string", "");
-        return !backgroundLayout ? (null) :
-            (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-                isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+        return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
+            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.backgroundView;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 1f8d22ab0..470c0c2f8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -170,7 +170,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
-        e.stopPropagation();
+        // e.stopPropagation();
     }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 294e17720..f404b7bc6 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed, trace } from "mobx";
 import { observer } from "mobx-react";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -45,7 +45,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     select: (ctrl: boolean) => void,
     layoutKey: string
 }> {
-    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", "<p>Error loading layout data</p>"); }
+    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", this.props.layoutKey === "layout" ? "<p>Error loading layout data</p>" : ""); }
 
     CreateBindings(): JsxBindings {
         return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
@@ -58,17 +58,17 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         }
         return new List<string>();
     }
-    set templates(templates: List<string>) { this.props.Document.templates = templates; }
-    get finalLayout() {
+    @computed get finalLayout() {
         const baseLayout = this.layout;
         let base = baseLayout;
         let layout = baseLayout;
 
-        // bcz: templates are intended only for a document's primary layout (not background).  However, 
+        // bcz: templates are intended only for a document's primary layout or overlay (not background).  However, 
         // a DocumentContentsView is used to render  annotation overlays, so we detect that here 
         // by checking the layoutKey.  This should probably be moved into
         // a prop so that the overlay can explicitly turn off templates.
-        if (this.props.layoutKey !== "backgroundLayout") {
+        if ((this.props.layoutKey === "overlayLayout" && StrCast(this.props.Document.layout).indexOf("CollectionView") !== -1) ||
+            (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
             this.templates.forEach(template => {
                 let self = this;
                 function convertConstantsToNative(match: string, offset: number, x: string) {
@@ -84,6 +84,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8d2f1c780..ee6000da7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -154,7 +154,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let text = StrCast(this.props.Document.documentText);
             if (text.startsWith("@@@")) {
                 this.props.Document.proto!.documentText = undefined;
-                this._editorView.dispatch(this._editorView.state.tr.insertText(text.substr(3)));
+                this._editorView.dispatch(this._editorView.state.tr.insertText(text.replace("@@@", "")));
             }
         }
 
-- 
cgit v1.2.3-70-g09d2


From 3be42131bc08024f06e0daec8d09e45bf3f1ddab Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 13 May 2019 16:21:28 -0400
Subject: a bunch of fixes to schemas, marquees, and an experimental feature to
 set a document's data with linking UI

---
 src/client/util/DragManager.ts                             |  6 ++++--
 src/client/views/DocumentDecorations.tsx                   |  2 ++
 src/client/views/collections/CollectionDockingView.tsx     |  7 ++-----
 src/client/views/collections/CollectionSchemaView.tsx      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx          |  4 ++--
 .../views/collections/collectionFreeForm/MarqueeView.tsx   |  8 +++-----
 src/client/views/nodes/DocumentContentsView.tsx            |  9 +++++++--
 src/client/views/nodes/DocumentView.tsx                    | 13 +++++++++++--
 src/client/views/nodes/ImageBox.tsx                        |  3 ++-
 src/client/views/nodes/KeyValueBox.tsx                     |  6 +++---
 src/client/views/nodes/LinkMenu.tsx                        |  2 +-
 src/client/views/nodes/PDFBox.tsx                          |  4 ++--
 src/new_fields/Doc.ts                                      | 14 +++++++++-----
 13 files changed, 49 insertions(+), 31 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2da0d5b51..26da34e67 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -105,7 +105,8 @@ export namespace DragManager {
         constructor(
             readonly x: number,
             readonly y: number,
-            readonly data: { [id: string]: any }
+            readonly data: { [id: string]: any },
+            readonly mods: string
         ) { }
     }
 
@@ -334,7 +335,8 @@ export namespace DragManager {
                     detail: {
                         x: e.x,
                         y: e.y,
-                        data: dragData
+                        data: dragData,
+                        mods: e.ctrlKey ? "Control" : ""
                     }
                 })
             );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4786b4de6..5aa3d804d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -239,6 +239,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             this._removeIcon = snapped;
         }
     }
+    @undoBatch
     @action
     onMinimizeUp = (e: PointerEvent): void => {
         e.stopPropagation();
@@ -270,6 +271,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         runInAction(() => this._minimizedX = this._minimizedY = 0);
     }
 
+    @undoBatch
     @action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
         let doc = selected[0].props.Document;
         let iconDoc = Docs.IconDocument(layoutString);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0aa067972..a755e0f91 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
 import * as GoldenLayout from "../../../client/goldenLayout";
-import { Doc, Field, Opt } from "../../../new_fields/Doc";
+import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
 import { FieldId, Id } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -313,10 +313,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     counter.DashDocId = tab.contentItem.config.props.documentId;
                     tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
                         () => {
-                            const lf = Cast(doc.linkedFromDocs, listSpec(Doc), []);
-                            const lt = Cast(doc.linkedToDocs, listSpec(Doc), []);
-                            let count = (lf ? lf.length : 0) + (lt ? lt.length : 0);
-                            counter.innerHTML = count;
+                            counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
                             tab.titleElement[0].textContent = doc.title;
                         }, { fireImmediately: true });
                     tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4984e26d1..9ecccc559 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -254,7 +254,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     get previewPanel() {
         // let doc = CompileScript(this.previewScript, { this: selected }, true)();
         const previewDoc = this.previewDocument;
-        return (<div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
+        return (<div className="collectionSchemaView-previewRegion" style={{ width: `${Math.max(0, this.previewRegionWidth - 1)}px` }}>
             {!previewDoc || !this.previewRegionWidth ? (null) : (
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
                     <DocumentView Document={previewDoc} isTopMost={false} selectOnLoad={false}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc86f38b6..c36c708db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -339,7 +339,7 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
             isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
-        return this.overlayView;
+        return this.props.Document.overlayLayout ? this.overlayView : (null);
     }
 }
 
@@ -350,7 +350,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
             isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
-        return this.backgroundView;
+        return this.props.Document.backgroundLayout ? this.backgroundView : (null);
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 1c29ebaae..24dea200e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -104,10 +104,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                             continue;
                         }
                         let doc = new Doc();
-                        columns.forEach((col, i) => {
-                            console.log(values[i] + " " + Number(values[i]).toString());
-                            doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined);
-                        });
+                        columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
                         if (groupAttr) {
                             doc["_group"] = groupAttr;
                         }
@@ -207,7 +204,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @undoBatch
     @action
     marqueeCommand = async (e: KeyboardEvent) => {
-        if (this._commandExecuted) {
+        if (this._commandExecuted || (e as any).propagationIsStopped) {
             return;
         }
         if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
@@ -224,6 +221,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e" || e.key === "p") {
             this._commandExecuted = true;
             e.stopPropagation();
+            (e as any).propagationIsStopped = true;
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
                 if (e.key === "s") {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f404b7bc6..3ddf8eb00 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -43,9 +43,14 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
-    layoutKey: string
+    layoutKey: string,
 }> {
-    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", this.props.layoutKey === "layout" ? "<p>Error loading layout data</p>" : ""); }
+    @computed get layout(): string {
+        return StrCast(this.props.Document[this.props.layoutKey],
+            this.props.Document.data ?
+                "<FieldView {...props} fieldKey='data' />" :
+                KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : ""));
+    }
 
     CreateBindings(): JsxBindings {
         return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index edc2158f0..b7ad9249a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -243,8 +243,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             const protoDest = destDoc.proto;
             const protoSrc = sourceDoc.proto;
-            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
-            de.data.droppedDocuments.push(destDoc);
+            if (de.mods == "Control") {
+                let src = protoSrc ? protoSrc : sourceDoc;
+                let dst = protoDest ? protoDest : destDoc;
+                dst.data = src;
+                dst.nativeWidth = src.nativeWidth;
+                dst.nativeHeight = src.nativeHeight;
+            }
+            else {
+                Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+                de.data.droppedDocuments.push(destDoc);
+            }
             e.stopPropagation();
         }
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f9659a4b2..05742f8d1 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -42,8 +42,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
+        console.log("title: " + this.Document.title);
         if (this._photoIndex === 0) {
-            this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+            Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
             this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
     }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 876a3c173..c9d665ceb 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,7 +18,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-
+    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -28,7 +28,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key === 'Enter') {
             if (this._keyInput && this._valueInput) {
-                let doc = FieldValue(Cast(this.props.Document.data, Doc));
+                let doc = this.fieldDocToLayout;
                 if (!doc) {
                     return;
                 }
@@ -60,7 +60,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     createTable = () => {
-        let doc = FieldValue(Cast(this.props.Document.data, Doc));
+        let doc = this.fieldDocToLayout;
         if (!doc) {
             return <tr><td>Loading...</td></tr>;
         }
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 7bf13d5f9..5dabfc30d 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -32,7 +32,7 @@ export class LinkMenu extends React.Component<Props> {
     render() {
         //get list of links from document
         let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs);
-        let linkTo = DocListCast(this.props.docView.props.Document.linkedToDoc);
+        let linkTo = DocListCast(this.props.docView.props.Document.linkedToDocs);
         if (this._editingLink === undefined) {
             return (
                 <div id="linkMenu-container">
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cb27b3f1b..ff8737192 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import * as htmlToImage from "html-to-image";
-import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css';
 import Measure from "react-measure";
@@ -223,7 +223,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             document.addEventListener("pointerup", this.onPointerUp);
         }
         if (this.props.isSelected() && e.buttons === 2) {
-            this._alt = true;
+            runInAction(() => this._alt = true);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointerup", this.onPointerUp);
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a8c9d28e1..4c837fcbd 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -173,11 +173,15 @@ export namespace Doc {
     export function MakeAlias(doc: Doc) {
         const alias = new Doc;
 
-        PromiseValue(Cast(doc.proto, Doc)).then(proto => {
-            if (proto) {
-                alias.proto = proto;
-            }
-        });
+        if (!doc.proto) {
+            alias.proto = doc;
+        } else {
+            PromiseValue(Cast(doc.proto, Doc)).then(proto => {
+                if (proto) {
+                    alias.proto = proto;
+                }
+            });
+        }
 
         return alias;
     }
-- 
cgit v1.2.3-70-g09d2


From 53351f6c5b448b93f2865eb38868bddb95ec4c1d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 May 2019 22:02:39 -0400
Subject: fixed templates on images.

---
 src/client/views/Templates.tsx                                     | 7 +++++--
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx      | 2 +-
 src/client/views/nodes/DocumentContentsView.tsx                    | 2 +-
 3 files changed, 7 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index a98870b04..ca0c04d1b 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -40,8 +40,11 @@ export namespace Templates {
     // export const BasicLayout = new Template("Basic layout", "{layout}");
 
     export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
-        `<div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
-            <FormattedTextBox {...props} fieldKey={"caption"} />
+        `<div>
+            <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+            <div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
+                <FormattedTextBox {...props} fieldKey={"caption"} />
+            </div>
         </div>` );
 
     export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eaa91fd74..b8bed795d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -363,7 +363,7 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
             isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
-        return this.props.Document.overlayLayout ? this.overlayView : (null);
+        return this.overlayView;
     }
 }
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 3ddf8eb00..f3d76c49b 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -64,7 +64,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return new List<string>();
     }
     @computed get finalLayout() {
-        const baseLayout = this.layout;
+        const baseLayout = this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;
         let base = baseLayout;
         let layout = baseLayout;
 
-- 
cgit v1.2.3-70-g09d2


From a2333e76fea26d2ce03272947d8a89973489fa46 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Thu, 16 May 2019 20:31:21 -0400
Subject: Made KVP not render while documents are loading

---
 src/client/views/nodes/DocumentContentsView.tsx | 12 +++++++++---
 src/new_fields/util.ts                          | 22 ++++++++++++++--------
 2 files changed, 23 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f3d76c49b..e96a0f068 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -46,10 +46,16 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     layoutKey: string,
 }> {
     @computed get layout(): string {
-        return StrCast(this.props.Document[this.props.layoutKey],
-            this.props.Document.data ?
+        const layout = Cast(this.props.Document[this.props.layoutKey], "string");
+        if (layout === undefined) {
+            return this.props.Document.data ?
                 "<FieldView {...props} fieldKey='data' />" :
-                KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : ""));
+                KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : "");
+        } else if (typeof layout === "string") {
+            return layout;
+        } else {
+            return "<p>Loading layout</p>";
+        }
     }
 
     CreateBindings(): JsxBindings {
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index a5f5e368b..3a16a6b42 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -55,6 +55,17 @@ export function getter(target: any, prop: string | symbol | number, receiver: an
     }
     return getField(target, prop);
 }
+function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) {
+    if (!protoField) return undefined;
+    let field = protoField[prop];
+    if (field instanceof Promise) {
+        cb && field.then(cb);
+        return field;
+    } else {
+        cb && cb(field);
+        return field;
+    }
+}
 
 //TODO The callback parameter is never being passed in currently, so we should be able to get rid of it.
 export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
@@ -65,14 +76,9 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea
     if (field === undefined && !ignoreProto) {
         const proto = getField(target, "proto", true);
         if (proto instanceof Doc) {
-            let field = proto[prop];
-            if (field instanceof Promise) {
-                callback && field.then(callback);
-                return undefined;
-            } else {
-                callback && callback(field);
-                return field;
-            }
+            return getProtoField(proto, prop, callback);
+        } else if (proto instanceof Promise) {
+            return proto.then(async proto => getProtoField(proto, prop, callback));
         }
     }
     callback && callback(field);
-- 
cgit v1.2.3-70-g09d2


From 530dc40f7519b8288d91db61103468ab713dfbdd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 16 May 2019 21:02:15 -0400
Subject: fixed some small layout issues related to isButton and  templates

---
 src/client/views/Templates.tsx                  | 4 ++--
 src/client/views/nodes/DocumentContentsView.tsx | 8 ++++++--
 src/client/views/nodes/DocumentView.tsx         | 3 +++
 src/client/views/nodes/ImageBox.scss            | 6 +++++-
 src/client/views/nodes/ImageBox.tsx             | 3 +--
 src/client/views/nodes/PDFBox.tsx               | 1 -
 6 files changed, 17 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 5d29e68ae..5a99b3d90 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -74,8 +74,8 @@ export namespace Templates {
     export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
         return (`<div>
                 <div style="height:100%; width:100%; position:absolute;">{layout}</div>
-                <div style="height:${height}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
-                    <ImageBox id="isExpander" {...props} PanelWidth={${width}} fieldKey={"${field}"} />
+                <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
+                    <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} />
                 </div>
             </div>`);
     }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f3d76c49b..ace8d4aa5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -21,7 +21,7 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
 import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -76,9 +76,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
             this.templates.forEach(template => {
                 let self = this;
+                // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
+                // than the width/height of the containing document
                 function convertConstantsToNative(match: string, offset: number, x: string) {
                     let px = Number(match.replace("px", ""));
-                    return `${px * self.props.ScreenToLocalTransform().Scale}px`;
+                    return `${Math.min(NumCast(self.props.Document.height, 0),
+                        Math.min(NumCast(self.props.Document.width, 0),
+                            px * self.props.ScreenToLocalTransform().Scale))}px`;
                 }
                 let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
                 layout = nativizedTemplate.replace("{layout}", base);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 428dd9b36..40ad55d25 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -224,6 +224,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (doc.isButton && !doc.nativeWidth) {
             doc.nativeWidth = this.props.Document[WidthSym]();
             doc.nativeHeight = this.props.Document[HeightSym]();
+        } else {
+
+            doc.nativeWidth = doc.nativeHeight = undefined;
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 2316a050e..f1b73a676 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -25,7 +25,11 @@
 }
 
 .imageBox-cont img {
-    height: 100%;
+    height: auto;
+    width:100%;
+}
+.imageBox-cont-interactive img {
+    height: auto;
     width:100%;
 }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3cc60a6c5..a06adfcf0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -42,8 +42,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        console.log("title: " + this.Document.title);
-        if (this._photoIndex === 0) {
+        if (this._photoIndex === 0 && (this.props as any).id !== "isExpander") {
             Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
             this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f6328fa83..e71ac4924 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -284,7 +284,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
     @computed
     get pdfPage() {
-        console.log(this.curPage);
         return <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
     }
     @computed
-- 
cgit v1.2.3-70-g09d2

