From f53ba030a86bf3fc384ff3c00efcf3c1f0497b9b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 19 May 2019 18:10:53 -0400
Subject: cleaned up context menu icons.

---
 src/client/views/nodes/DocumentView.tsx | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 38f3db19f..9e86b411e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -19,7 +19,6 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
@@ -39,6 +38,13 @@ library.add(faLayerGroup);
 library.add(faAlignCenter);
 library.add(faCaretSquareRight);
 library.add(faSquare);
+library.add(faConciergeBell);
+library.add(faFolder);
+library.add(faMapPin);
+library.add(faLink);
+library.add(faFingerprint);
+library.add(faCrosshairs);
+library.add(faDesktop);
 
 const linkSchema = createSchema({
     title: "string",
@@ -308,21 +314,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.preventDefault();
 
         const cm = ContextMenu.Instance;
-        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" });
-        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "expand-arrows-alt" });
-        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
-        cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" });
-        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "expand-arrows-alt" });
+        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "desktop" });
+        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
         cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
+        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+        cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
+        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
                 CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
-            }, icon: "expand-arrows-alt"
+            }, icon: "search"
         });
-        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "expand-arrows-alt" });
-        cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "expand-arrows-alt" });
-        cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "expand-arrows-alt" });
+        cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
+        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+        cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         if (!this.topMost) {
             // DocumentViews should stop propagation of this event
-- 
cgit v1.2.3-70-g09d2


From c103acbe8f8df919e87f9a18872a94eb8bc1a001 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 19 May 2019 19:55:26 -0400
Subject: fixed some small things with presentation and icon

---
 src/client/views/DocumentDecorations.tsx | 6 +++---
 src/client/views/MainView.tsx            | 2 +-
 src/client/views/nodes/DocumentView.tsx  | 3 ++-
 src/client/views/nodes/IconBox.tsx       | 1 -
 4 files changed, 6 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5ec090f05..319e91337 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -276,16 +276,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let doc = selected[0].props.Document;
         let iconDoc = Docs.IconDocument(layoutString);
         iconDoc.isButton = true;
-        iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+        iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon";
         iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
-        iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+        //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
         iconDoc.proto!.isMinimized = false;
         iconDoc.width = Number(MINIMIZED_ICON_SIZE);
         iconDoc.height = Number(MINIMIZED_ICON_SIZE);
         iconDoc.x = NumCast(doc.x);
         iconDoc.y = NumCast(doc.y) - 24;
         iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document.proto!));
-        doc.minimizedDoc = iconDoc;
+        selected.length === 1 && (doc.minimizedDoc = iconDoc);
         selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
         return iconDoc;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cdb105e21..400562f12 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -47,7 +47,7 @@ export class MainView extends React.Component {
     private set mainContainer(doc: Opt<Doc>) {
         if (doc) {
             if (!("presentationView" in doc)) {
-                doc.presentationView = new Doc();
+                doc.presentationView = Docs.TreeDocument([], { title: "Presentation" });
             }
             CurrentUserUtils.UserDocument.activeWorkspace = doc;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e86b411e..5f218fd1f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -128,7 +128,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
-        this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
+        this._reactionDisposer = reaction(() => [DocListCast(this.props.Document.maximizedDocs).map(md => md.title),
+        this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             () => {
                 let maxDoc = DocListCast(this.props.Document.maximizedDocs);
                 if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index b42eb44a5..00021bc78 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -12,7 +12,6 @@ import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
 import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
-import { listSpec } from "../../../new_fields/Schema";
 
 
 library.add(faCaretUp);
-- 
cgit v1.2.3-70-g09d2


From af2346983eae1145167b70faf96a9aec0ca82427 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 20 May 2019 00:09:47 -0400
Subject: Fixed a bunch of demo bugs Moved Field Symbols to separate file
 Editing is mostly working in debug viewer

---
 deploy/debug/viewer.html                           |   1 +
 src/client/DocServer.ts                            |   3 +-
 src/client/northstar/dash-fields/HistogramField.ts |   7 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   |   2 +-
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/SearchUtil.ts                      |   2 +-
 src/client/util/TooltipTextMenu.tsx                |   2 +-
 src/client/util/type_decls.d                       | 128 +++++++--------------
 src/client/views/EditableView.tsx                  |  10 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PresentationView.tsx              |   2 +-
 src/client/views/SearchBox.tsx                     |   2 +-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 .../views/collections/CollectionDockingView.tsx    |  14 ++-
 src/client/views/collections/CollectionPDFView.tsx |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../views/collections/CollectionVideoView.tsx      |   2 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/ParentDocumentSelector.tsx   |   2 +-
 .../CollectionFreeFormLinksView.tsx                |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   3 -
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/KeyValueBox.tsx             |   4 +-
 src/client/views/nodes/KeyValuePair.tsx            |   4 +-
 src/client/views/nodes/LinkMenu.tsx                |   2 +-
 src/debug/Viewer.tsx                               |  53 +++++++--
 src/new_fields/CursorField.ts                      |   7 +-
 src/new_fields/DateField.ts                        |   7 +-
 src/new_fields/Doc.ts                              |  43 ++++---
 src/new_fields/FieldSymbols.ts                     |  10 ++
 src/new_fields/HtmlField.ts                        |   7 +-
 src/new_fields/IconField.ts                        |   7 +-
 src/new_fields/InkField.ts                         |   7 +-
 src/new_fields/List.ts                             |  16 ++-
 src/new_fields/ObjectField.ts                      |   7 +-
 src/new_fields/Proxy.ts                            |   9 +-
 src/new_fields/RefField.ts                         |   5 +-
 src/new_fields/RichTextField.ts                    |   7 +-
 src/new_fields/URLField.ts                         |   9 +-
 src/new_fields/util.ts                             |   7 +-
 42 files changed, 240 insertions(+), 173 deletions(-)
 create mode 100644 src/new_fields/FieldSymbols.ts

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

diff --git a/deploy/debug/viewer.html b/deploy/debug/viewer.html
index 3785a6602..8c265ccb8 100644
--- a/deploy/debug/viewer.html
+++ b/deploy/debug/viewer.html
@@ -3,6 +3,7 @@
 <head>
     <title>Document Debugger</title>
     <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
 </head>
 
 <body>
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index f1b50d5a0..cbcf751ee 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -3,7 +3,8 @@ import { MessageStore } from "./../server/Message";
 import { Opt } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
-import { RefField, HandleUpdate, Id } from '../new_fields/RefField';
+import { RefField } from '../new_fields/RefField';
+import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
 
 export namespace DocServer {
     const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 1ee2189b9..31040a474 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -3,10 +3,11 @@ import { custom, serializable } from "serializr";
 import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
 import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { ObjectField, Copy } from "../../../new_fields/ObjectField";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { OmitKeys } from "../../../Utils";
 import { Deserializable } from "../../util/SerializationHelper";
+import { Copy, ToScriptString } from "../../../new_fields/FieldSymbols";
 
 function serialize(field: HistogramField) {
     let obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
@@ -55,4 +56,8 @@ export class HistogramField extends ObjectField {
         let z = this.HistoOp.Copy;
         return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index eb1ad69b7..a9646ed31 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -19,7 +19,7 @@ import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
 import { Cast } from "../../../new_fields/Types";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 
 @observer
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index be448098a..a5e768dcf 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -5,10 +5,10 @@ import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { undoBatch } from './UndoManager';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../new_fields/RefField';
 import { CollectionView } from '../views/collections/CollectionView';
 import { CollectionPDFView } from '../views/collections/CollectionPDFView';
 import { CollectionVideoView } from '../views/collections/CollectionVideoView';
+import { Id } from '../../new_fields/FieldSymbols';
 
 
 export class DocumentManager {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 4ccff0d1b..e8eb70837 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,7 +1,7 @@
 import * as rp from 'request-promise';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 
 export namespace SearchUtil {
     export function Search(query: string, returnDocs: true): Promise<Doc[]>;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 4d40d09b2..a1f80120f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -22,12 +22,12 @@ import { throwStatement } from "babel-types";
 import { View } from "@react-pdf/renderer";
 import { DragManager } from "./DragManager";
 import { Doc, Opt, Field } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/RefField";
 import { Utils } from "../northstar/utils/Utils";
 import { DocServer } from "../DocServer";
 import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { DocumentManager } from "./DocumentManager";
+import { Id } from "../../new_fields/FieldSymbols";
 
 const SVG = "http://www.w3.org/2000/svg";
 
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 47c3481b2..51114d0e2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -119,104 +119,54 @@ interface URL {
     username: string;
     toJSON(): string;
 }
+interface PromiseLike<T> {
+    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
+}
+interface Promise<T> {
+    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
+    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
+}
 
-declare type FieldId = string;
+declare const Update: unique symbol;
+declare const Self: unique symbol;
+declare const SelfProxy: unique symbol;
+declare const HandleUpdate: unique symbol;
+declare const Id: unique symbol;
+declare const OnUpdate: unique symbol;
+declare const Parent: unique symbol;
+declare const Copy: unique symbol;
+declare const ToScriptString: unique symbol;
 
-declare abstract class Field {
-    Id: FieldId;
-    abstract ToScriptString(): string;
-    abstract TrySetValue(value: any): boolean;
-    abstract GetValue(): any;
-    abstract Copy(): Field;
-}
+declare abstract class RefField {
+    readonly [Id]: FieldId;
+
+    constructor(id?: FieldId);
+    protected [HandleUpdate]?(diff: any): void;
 
-declare abstract class BasicField<T> extends Field {
-    constructor(data: T);
-    Data: T;
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
+    abstract [ToScriptString](): string;
 }
 
-declare class TextField extends BasicField<string>{
-    constructor();
-    constructor(data: string);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class ImageField extends BasicField<URL>{
-    constructor();
-    constructor(data: URL);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class HtmlField extends BasicField<string>{
-    constructor();
-    constructor(data: string);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class NumberField extends BasicField<number>{
-    constructor();
-    constructor(data: number);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class WebField extends BasicField<URL>{
-    constructor();
-    constructor(data: URL);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class ListField<T> extends BasicField<T[]>{
-    constructor();
-    constructor(data: T[]);
-    ToScriptString(): string;
-    Copy(): Field;
-}
-declare class Key extends Field {
-    constructor(name:string);
-    Name: string;
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
-    Copy(): Field;
-    ToScriptString(): string;
-}
-declare type FIELD_WAITING = null;
-declare type Opt<T> = T | undefined;
-declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
-// @ts-ignore
-declare class Document extends Field {
-    TrySetValue(value: any): boolean;
-    GetValue(): any;
-    Copy(): Field;
-    ToScriptString(): string;
+declare abstract class ObjectField {
+    protected [OnUpdate](diff?: any): void;
+    private [Parent]?: RefField | ObjectField;
+    abstract [Copy](): ObjectField;
 
-    Width(): number;
-    Height(): number;
-    Scale(): number;
-    Title: string;
+    abstract [ToScriptString](): string;
+}
+declare type FieldId = string;
 
-    Get(key: Key): FieldValue<Field>;
-    GetAsync(key: Key, callback: (field: Field) => void): boolean;
-    GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void;
-    GetT<T extends Field>(key: Key, ctor: { new(): T }): FieldValue<T>;
-    GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }): T;
-    GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T;
-    GetHtml(key: Key, defaultVal: string): string;
-    GetNumber(key: Key, defaultVal: number): number;
-    GetText(key: Key, defaultVal: string): string;
-    GetList<T extends Field>(key: Key, defaultVal: T[]): T[];
-    Set(key: Key, field: Field | undefined): void;
-    SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }): void;
-    SetText(key: Key, value: string): void;
-    SetNumber(key: Key, value: number): void;
-    GetPrototype(): FieldValue<Document>;
-    GetAllPrototypes(): Document[];
-    MakeDelegate(): Document;
+declare type Field = number | string | boolean | ObjectField | RefField;
+
+declare type Opt<T> = T | undefined;
+declare class Doc extends RefField {
+    [key: string]: Field | undefined;
+    [ToScriptString](): string;
 }
 
-declare const KeyStore: {
-    [name: string]: Key;
+declare class ListImpl<T extends Field> extends ObjectField {
+    [index: number]: T | (T extends RefField ? Promise<T> : never);
+    [ToScriptString](): string;
+    [Copy](): ObjectField;
 }
 
 // @ts-ignore
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 78143ccda..c946d68e1 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -22,7 +22,7 @@ export interface EditableProps {
      * The contents to render when not editing
      */
     contents: any;
-    height: number;
+    height?: number;
     display?: string;
     oneLine?: boolean;
 }
@@ -53,6 +53,12 @@ export class EditableView extends React.Component<EditableProps> {
         }
     }
 
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.editing = true;
+        e.stopPropagation();
+    }
+
     render() {
         if (this.editing) {
             return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
@@ -60,7 +66,7 @@ export class EditableView extends React.Component<EditableProps> {
         } else {
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={action(() => this.editing = true)} >
+                    onClick={this.onClick} >
                     <span>{this.props.contents}</span>
                 </div>
             );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 400562f12..9edbba997 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -29,7 +29,7 @@ import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
 import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { DocServer } from '../DocServer';
 import { listSpec } from '../../new_fields/Schema';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 import { HistoryUtil } from '../util/History';
 import { CollectionBaseView } from './collections/CollectionBaseView';
 
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 9c37e9000..ce679aa0a 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -7,7 +7,7 @@ import { Utils } from "../../Utils";
 import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
 import { listSpec } from "../../new_fields/Schema";
 import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types";
-import { Id } from "../../new_fields/RefField";
+import { Id } from "../../new_fields/FieldSymbols";
 import { List } from "../../new_fields/List";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 6e64e1af1..8efd8d266 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -16,7 +16,7 @@ import { isString } from 'util';
 import { constant } from 'async';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/RefField';
+import { Id } from '../../new_fields/FieldSymbols';
 import { DocumentManager } from '../util/DocumentManager';
 import { SetupDrag } from '../util/DragManager';
 import { Docs } from '../documents/Documents';
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 84ffbac36..5686ccfef 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -7,8 +7,8 @@ import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Typ
 import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc';
 import { listSpec } from '../../../new_fields/Schema';
 import { List } from '../../../new_fields/List';
-import { Id } from '../../../new_fields/RefField';
 import { SelectionManager } from '../../util/SelectionManager';
+import { Id } from '../../../new_fields/FieldSymbols';
 
 export enum CollectionViewType {
     Invalid,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 483209f86..e904358a9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -6,7 +6,7 @@ import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
 import * as GoldenLayout from "../../../client/goldenLayout";
 import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
-import { FieldId, Id } from "../../../new_fields/RefField";
+import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
@@ -21,6 +21,7 @@ import React = require("react");
 import { ParentDocSelector } from './ParentDocumentSelector';
 import { DocumentManager } from '../../util/DocumentManager';
 import { CollectionViewType } from './CollectionBaseView';
+import { Id } from '../../../new_fields/FieldSymbols';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -75,7 +76,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @undoBatch
     @action
-    public CloseRightSplit(document: Doc): boolean {
+    public CloseRightSplit = (document: Doc): boolean => {
         let retVal = false;
         if (this._goldenLayout.root.contentItems[0].isRow) {
             retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
@@ -118,7 +119,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
     @action
-    public AddRightSplit(document: Doc, minimize: boolean = false) {
+    public AddRightSplit = (document: Doc, minimize: boolean = false) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -155,7 +156,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return newContentItem;
     }
     @action
-    public AddTab(stack: any, document: Doc) {
+    public AddTab = (stack: any, document: Doc) => {
         let docs = Cast(this.props.Document.data, listSpec(Doc));
         if (docs) {
             docs.push(document);
@@ -412,10 +413,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
-    _stack: any;
+    get _stack(): any {
+        return (this.props as any).glContainer.parent.parent;
+    }
     constructor(props: any) {
         super(props);
-        this._stack = (this.props as any).glContainer.parent.parent;
         DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
     }
 
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index a6614da21..5e51437a4 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -8,7 +8,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
 import { emptyFunction } from "../../../Utils";
 import { NumCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 
 @observer
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f15da41ff..b25b48339 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -23,7 +23,7 @@ import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fie
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { Docs } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 72fa69cb1..8ad495762 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -11,7 +11,7 @@ import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
 import { Doc, DocListCast } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/RefField';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { ContextMenu } from '../ContextMenu';
 import { undoBatch } from '../../util/UndoManager';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 9ab959f3c..27f23a1a8 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -7,7 +7,7 @@ import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import { emptyFunction } from "../../../Utils";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { VideoBox } from "../nodes/VideoBox";
 import { NumCast } from "../../../new_fields/Types";
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 59c763be8..bfdef8e8c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
-import { Id } from '../../../new_fields/RefField';
+import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 52f7914f3..4d07c31a7 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -3,7 +3,7 @@ import './ParentDocumentSelector.scss';
 import { Doc } from "../../../new_fields/Doc";
 import { observer } from "mobx-react";
 import { observable, action, runInAction } from "mobx";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { SearchUtil } from "../../util/SearchUtil";
 import { CollectionDockingView } from "./CollectionDockingView";
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index d5ce4e1e7..e1ff715d1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -11,7 +11,7 @@ import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { listSpec } from "../../../../new_fields/Schema";
 import { List } from "../../../../new_fields/List";
-import { Id } from "../../../../new_fields/RefField";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9cb8443f4..ee6f4821f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -22,9 +22,9 @@ import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Sc
 import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
 import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types";
 import { pageSchema } from "../../nodes/ImageBox";
-import { Id } from "../../../../new_fields/RefField";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { HistoryUtil } from "../../../util/History";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 export const panZoomSchema = createSchema({
     panX: "number",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4587c2227..2029b91e5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,9 +17,6 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { ImageField } from "../../../../new_fields/URLField";
 import { Template, Templates } from "../../Templates";
-import { Gateway } from "../../../northstar/manager/Gateway";
-import { DocServer } from "../../../DocServer";
-import { Id } from "../../../../new_fields/RefField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5f218fd1f..87c88f57c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,8 +4,7 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
-import { Copy, ObjectField } from "../../../new_fields/ObjectField";
-import { Id } from "../../../new_fields/RefField";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -29,6 +28,7 @@ import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
+import { Id, Copy } from '../../../new_fields/FieldSymbols';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 86437a6c1..8cb576786 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -8,7 +8,7 @@ import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
 import React = require("react");
 import { NumCast, Cast, FieldValue } from "../../../new_fields/Types";
-import { Doc, IsField } from "../../../new_fields/Doc";
+import { Doc, Field } from "../../../new_fields/Doc";
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
@@ -41,7 +41,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 let res = script.run();
                 if (!res.success) return;
                 const field = res.result;
-                if (IsField(field)) {
+                if (Field.IsField(field)) {
                     realDoc[this._keyInput] = field;
                 }
                 this._keyInput = "";
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 4f7919f50..7a88985c0 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -9,7 +9,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import "./KeyValuePair.scss";
 import React = require("react");
-import { Doc, Opt, IsField } from '../../../new_fields/Doc';
+import { Doc, Opt, Field } from '../../../new_fields/Doc';
 import { FieldValue } from '../../../new_fields/Types';
 
 // Represents one row in a key value plane
@@ -75,7 +75,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             let res = script.run();
                             if (!res.success) return false;
                             const field = res.result;
-                            if (IsField(field)) {
+                            if (Field.IsField(field)) {
                                 props.Document[props.fieldKey] = field;
                                 return true;
                             }
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 4cf798249..3f09d6214 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -7,7 +7,7 @@ import './LinkMenu.scss';
 import React = require("react");
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 interface Props {
     docView: DocumentView;
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 720e1640a..4314e2132 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,11 +3,27 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-import { Doc, Field, FieldResult } from '../new_fields/Doc';
+import { Doc, Field, FieldResult, Opt } from '../new_fields/Doc';
 import { DocServer } from '../client/DocServer';
-import { Id } from '../new_fields/RefField';
+import { Id } from '../new_fields/FieldSymbols';
 import { List } from '../new_fields/List';
 import { URLField } from '../new_fields/URLField';
+import { EditableView } from '../client/views/EditableView';
+import { CompileScript } from '../client/util/Scripting';
+
+function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean;
+function applyToDoc(doc: { [index: number]: FieldResult }, key: number, scriptString: string): boolean;
+function applyToDoc(doc: any, key: string | number, scriptString: string): boolean {
+    let script = CompileScript(scriptString, { addReturn: true, params: { this: doc instanceof Doc ? Doc.name : List.name } });
+    if (!script.compiled) {
+        return false;
+    }
+    const res = script.run({ this: doc });
+    if (!res.success) return false;
+    if (!Field.IsField(res.result)) return false;
+    doc[key] = res.result;
+    return true;
+}
 
 configure({
     enforceActions: "observed"
@@ -18,12 +34,18 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
     @observable
     expanded = false;
 
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.expanded = !this.expanded;
+        e.stopPropagation();
+    }
+
     render() {
         let content;
         if (this.expanded) {
             content = (
                 <div>
-                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} />)}
+                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} setValue={value => applyToDoc(this.props.field, index, value)} />)}
                 </div>
             );
         } else {
@@ -31,7 +53,7 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
         }
         return (
             <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                <button onClick={this.onClick}>Toggle</button>
                 {content}
             </div >
         );
@@ -42,6 +64,13 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
 class DocumentViewer extends React.Component<{ field: Doc }> {
     @observable
     expanded = false;
+
+    @action
+    onClick = (e: React.MouseEvent) => {
+        this.expanded = !this.expanded;
+        e.stopPropagation();
+    }
+
     render() {
         let content;
         if (this.expanded) {
@@ -50,7 +79,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
                 return (
                     <div key={key}>
                         <b>({key}): </b>
-                        <DebugViewer field={this.props.field[key]}></DebugViewer>
+                        <DebugViewer field={this.props.field[key]} setValue={value => applyToDoc(this.props.field, key, value)}></DebugViewer>
                     </div>
                 );
             });
@@ -67,7 +96,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
         }
         return (
             <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+                <button onClick={this.onClick}>Toggle</button>
                 {content}
             </div >
         );
@@ -75,7 +104,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
 }
 
 @observer
-class DebugViewer extends React.Component<{ field: FieldResult }> {
+class DebugViewer extends React.Component<{ field: FieldResult, setValue(value: string): boolean }> {
 
     render() {
         let content;
@@ -90,10 +119,14 @@ class DebugViewer extends React.Component<{ field: FieldResult }> {
             content = <p>{field}</p>;
         } else if (field instanceof URLField) {
             content = <p>{field.url.href}</p>;
+        } else if (field instanceof Promise) {
+            return <p>Field loading</p>;
         } else {
-            content = <p>Unrecognized field type</p>;
+            return <p>Unrecognized field type</p>;
         }
-        return content;
+
+        return <EditableView GetValue={() => Field.toScriptString(field)} SetValue={this.props.setValue}
+            contents={content}></EditableView>;
     }
 }
 
@@ -129,7 +162,7 @@ class Viewer extends React.Component {
                     onChange={this.inputOnChange}
                     onKeyDown={this.onKeyPress} />
                 <div>
-                    {this.fields.map((field, index) => <DebugViewer field={field} key={index}></DebugViewer>)}
+                    {this.fields.map((field, index) => <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>)}
                 </div>
             </>
         );
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
index fc144222c..1be1ec3e0 100644
--- a/src/new_fields/CursorField.ts
+++ b/src/new_fields/CursorField.ts
@@ -1,7 +1,8 @@
-import { ObjectField, Copy, OnUpdate } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { observable } from "mobx";
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, createSimpleSchema, object } from "serializr";
+import { OnUpdate, ToScriptString, Copy } from "./FieldSymbols";
 
 export type CursorPosition = {
     x: number,
@@ -52,4 +53,8 @@ export default class CursorField extends ObjectField {
     [Copy]() {
         return new CursorField(this.data);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts
index c0a79f267..fc8abb9d9 100644
--- a/src/new_fields/DateField.ts
+++ b/src/new_fields/DateField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, date } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("date")
 export class DateField extends ObjectField {
@@ -15,4 +16,8 @@ export class DateField extends ObjectField {
     [Copy]() {
         return new DateField(this.date);
     }
+
+    [ToScriptString]() {
+        return `new DateField(new Date(${this.date.toISOString()}))`;
+    }
 }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 02dd34cb4..f4514c33e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -5,27 +5,33 @@ import { DocServer } from "../client/DocServer";
 import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
 import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
 import { listSpec } from "./Schema";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
-import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
-
-export function IsField(field: any): field is Field {
-    return (typeof field === "string")
-        || (typeof field === "number")
-        || (typeof field === "boolean")
-        || (field instanceof ObjectField)
-        || (field instanceof RefField);
+import { ObjectField } from "./ObjectField";
+import { RefField, FieldId } from "./RefField";
+import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
+
+export namespace Field {
+    export function toScriptString(field: Field): string {
+        if (typeof field === "string") {
+            return `"${field}"`;
+        } else if (typeof field === "number" || typeof field === "boolean") {
+            return String(field);
+        } else {
+            return field[ToScriptString]();
+        }
+    }
+    export function IsField(field: any): field is Field {
+        return (typeof field === "string")
+            || (typeof field === "number")
+            || (typeof field === "boolean")
+            || (field instanceof ObjectField)
+            || (field instanceof RefField);
+    }
 }
 export type Field = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
 export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
 
-export const Update = Symbol("Update");
-export const Self = Symbol("Self");
-export const SelfProxy = Symbol("SelfProxy");
-export const WidthSym = Symbol("Width");
-export const HeightSym = Symbol("Height");
-
 /**
  * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.  
  * If a default value is given, that will be returned instead of undefined.  
@@ -43,6 +49,9 @@ export function DocListCast(field: FieldResult): Doc[] {
     return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
 }
 
+export const WidthSym = Symbol("Width");
+export const HeightSym = Symbol("Height");
+
 @Deserializable("doc").withFields(["id"])
 export class Doc extends RefField {
     constructor(id?: FieldId, forceSave?: boolean) {
@@ -102,6 +111,10 @@ export class Doc extends RefField {
     public [WidthSym] = () => NumCast(this[SelfProxy].width);  // bcz: is this the right way to access width/height?   it didn't work with : this.width
     public [HeightSym] = () => NumCast(this[SelfProxy].height);
 
+    [ToScriptString]() {
+        return "invalid";
+    }
+
     public [HandleUpdate](diff: any) {
         console.log(diff);
         const set = diff.$set;
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
new file mode 100644
index 000000000..a436dcf2b
--- /dev/null
+++ b/src/new_fields/FieldSymbols.ts
@@ -0,0 +1,10 @@
+
+export const Update = Symbol("Update");
+export const Self = Symbol("Self");
+export const SelfProxy = Symbol("SelfProxy");
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export const Copy = Symbol("Copy");
+export const ToScriptString = Symbol("Copy");
\ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index d998746bb..f952acff9 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("html")
 export class HtmlField extends ObjectField {
@@ -15,4 +16,8 @@ export class HtmlField extends ObjectField {
     [Copy]() {
         return new HtmlField(this.html);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
index 1a928389d..62b2cd254 100644
--- a/src/new_fields/IconField.ts
+++ b/src/new_fields/IconField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("icon")
 export class IconField extends ObjectField {
@@ -15,4 +16,8 @@ export class IconField extends ObjectField {
     [Copy]() {
         return new IconField(this.icon);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 2d75f8a19..4e3b7abe0 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
 import { deepCopy } from "../Utils";
 
 export enum InkTool {
@@ -40,4 +41,8 @@ export class InkField extends ObjectField {
     [Copy]() {
         return new InkField(deepCopy(this.inkData));
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 70e36f911..f1e4c4721 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,11 +1,12 @@
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Field, Update, Self, FieldResult, SelfProxy } from "./Doc";
+import { Field } from "./Doc";
 import { setter, getter, deleteProperty, updateFunction } from "./util";
 import { serializable, alias, list } from "serializr";
 import { observable, action } from "mobx";
-import { ObjectField, OnUpdate, Copy, Parent } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
+import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, Copy } from "./FieldSymbols";
 
 const listHandlers: any = {
     /// Mutator methods
@@ -225,7 +226,7 @@ type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
 
 @Deserializable("list")
 class ListImpl<T extends Field> extends ObjectField {
-    constructor(fields: T[] = []) {
+    constructor(fields?: T[]) {
         super();
         const list = new Proxy<this>(this, {
             set: setter,
@@ -244,7 +245,9 @@ class ListImpl<T extends Field> extends ObjectField {
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
         this[SelfProxy] = list;
-        (list as any).push(...fields);
+        if (fields) {
+            (list as any).push(...fields);
+        }
         return list;
     }
 
@@ -284,6 +287,11 @@ class ListImpl<T extends Field> extends ObjectField {
 
     private [Self] = this;
     private [SelfProxy]: any;
+
+    [ToScriptString]() {
+        return "invalid";
+        // return `new List([${(this as any).map((field => Field.toScriptString(field))}])`;
+    }
 }
 export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
 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/ObjectField.ts b/src/new_fields/ObjectField.ts
index 51768c6db..5f4a6f8fb 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -1,14 +1,13 @@
 import { Doc } from "./Doc";
 import { RefField } from "./RefField";
-
-export const OnUpdate = Symbol("OnUpdate");
-export const Parent = Symbol("Parent");
-export const Copy = Symbol("Copy");
+import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols";
 
 export abstract class ObjectField {
     protected [OnUpdate](diff?: any) { }
     private [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
+
+    abstract [ToScriptString](): string;
 }
 
 export namespace ObjectField {
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index fd99ae1c0..130ec066e 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -3,8 +3,9 @@ import { FieldWaiting } from "./Doc";
 import { primitive, serializable } from "serializr";
 import { observable, action } from "mobx";
 import { DocServer } from "../client/DocServer";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Copy } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
+import { Id, Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("proxy")
 export class ProxyField<T extends RefField> extends ObjectField {
@@ -26,6 +27,10 @@ export class ProxyField<T extends RefField> extends ObjectField {
         return new ProxyField<T>(this.fieldId);
     }
 
+    [ToScriptString]() {
+        return "invalid";
+    }
+
     @serializable(primitive())
     readonly fieldId: string = "";
 
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
index 202c65f21..75ce4287f 100644
--- a/src/new_fields/RefField.ts
+++ b/src/new_fields/RefField.ts
@@ -1,9 +1,8 @@
 import { serializable, primitive, alias } from "serializr";
 import { Utils } from "../Utils";
+import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols";
 
 export type FieldId = string;
-export const HandleUpdate = Symbol("HandleUpdate");
-export const Id = Symbol("Id");
 export abstract class RefField {
     @serializable(alias("id", primitive()))
     private __id: FieldId;
@@ -15,4 +14,6 @@ export abstract class RefField {
     }
 
     protected [HandleUpdate]?(diff: any): void;
+
+    abstract [ToScriptString](): string;
 }
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index eb30e76de..89d077a47 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -1,6 +1,7 @@
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
 import { serializable } from "serializr";
 import { Deserializable } from "../client/util/SerializationHelper";
+import { Copy, ToScriptString } from "./FieldSymbols";
 
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
@@ -15,4 +16,8 @@ export class RichTextField extends ObjectField {
     [Copy]() {
         return new RichTextField(this.Data);
     }
+
+    [ToScriptString]() {
+        return "invalid";
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index d00a95a16..a6f8f1cc5 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -1,6 +1,7 @@
 import { Deserializable } from "../client/util/SerializationHelper";
 import { serializable, custom } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { ToScriptString, Copy } from "./FieldSymbols";
 
 function url() {
     return custom(
@@ -13,7 +14,7 @@ function url() {
     );
 }
 
-export class URLField extends ObjectField {
+export abstract class URLField extends ObjectField {
     @serializable(url())
     readonly url: URL;
 
@@ -22,6 +23,10 @@ export class URLField extends ObjectField {
         this.url = url;
     }
 
+    [ToScriptString]() {
+        return `new ${this.constructor.name}(new URL(${this.url.href}))`;
+    }
+
     [Copy](): this {
         return new (this.constructor as any)(this.url);
     }
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index d94994a07..65a37a0d1 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -1,11 +1,12 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Update, Doc, Field } from "./Doc";
+import { Doc, Field } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField } from "./Proxy";
 import { FieldValue } from "./Types";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
 import { action } from "mobx";
+import { Parent, OnUpdate, Update, Id } from "./FieldSymbols";
 
 export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
     if (SerializationHelper.IsSerializing()) {
-- 
cgit v1.2.3-70-g09d2


From 30fdae9e869e0f132c76b4fa99b784e75d6c8dae Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 22 May 2019 19:51:49 -0400
Subject: A bunch of fixes/changes

---
 src/client/documents/Documents.ts                  |  2 +
 src/client/util/DocumentManager.ts                 |  6 ++-
 src/client/views/InkingCanvas.scss                 | 42 ++++++++++++--------
 src/client/views/SearchBox.tsx                     | 10 +++--
 .../views/collections/CollectionPDFView.scss       | 39 ++++++++++--------
 src/client/views/collections/CollectionSubView.tsx |  4 ++
 .../views/collections/CollectionVideoView.tsx      | 46 +++++++++++++++++++++-
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 ++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 ++++-
 src/client/views/nodes/DocumentView.tsx            | 12 +++---
 src/client/views/nodes/ImageBox.tsx                |  6 ++-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 16 +++++++-
 src/client/views/nodes/VideoBox.tsx                |  8 +++-
 14 files changed, 153 insertions(+), 54 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 323534b2b..ae190a989 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -77,7 +77,9 @@ export namespace DocUtils {
             linkDoc.proto!.linkTags = "Default";
 
             linkDoc.proto!.linkedTo = target;
+            linkDoc.proto!.linkedToPage = target.curPage;
             linkDoc.proto!.linkedFrom = source;
+            linkDoc.proto!.linkedFromPage = source.curPage;
 
             let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
             if (!linkedFrom) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index f113ff1d7..65c4b9e4b 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -115,11 +115,11 @@ export class DocumentManager {
     }
 
     @undoBatch
-    public jumpToDocument = async (docDelegate: Doc, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void): Promise<void> => {
+    public jumpToDocument = async (docDelegate: Doc, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number): Promise<void> => {
         let doc = Doc.GetProto(docDelegate);
-        const page = NumCast(doc.page, undefined);
         const contextDoc = await Cast(doc.annotationOn, Doc);
         if (contextDoc) {
+            const page = NumCast(doc.page, linkPage || 0);
             const curPage = NumCast(contextDoc.curPage, page);
             if (page !== curPage) contextDoc.curPage = page;
         }
@@ -127,11 +127,13 @@ export class DocumentManager {
         // using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed
         if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
             docView.props.Document.libraryBrush = true;
+            if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
             docView.props.focus(docView.props.Document);
         } else {
             if (!contextDoc) {
                 const actualDoc = Doc.MakeAlias(docDelegate);
                 actualDoc.libraryBrush = true;
+                if (linkPage !== undefined) actualDoc.curPage = linkPage;
                 (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc);
             } else {
                 let contextView: DocumentView | null;
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index 8bcaf41dd..d95398f17 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,40 +1,50 @@
 @import "globalCssVariables";
 
 .inkingCanvas {
-    opacity:0.99;
+    opacity: 0.99;
+
     .jsx-parser {
-        position:absolute;
-        width:100%;
-        height:100%;
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        z-index: -1; // allows annotations to appear on videos when screen is full-size & ... 
     }
 }
-.inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect {
+
+.inkingCanvas-paths-ink,
+.inkingCanvas-paths-markers,
+.inkingCanvas-noSelect,
+.inkingCanvas-canSelect {
     position: absolute;
     top: 0;
-    left:0;
+    left: 0;
     width: 8192px;
     height: 8192px;
-    cursor:"crosshair";
+    cursor: "crosshair";
     pointer-events: auto;
-    
+
 }
-.inkingCanvas-canSelect, 
-.inkingCanvas-noSelect    {
-    top:-50000px;
-    left:-50000px;
+
+.inkingCanvas-canSelect,
+.inkingCanvas-noSelect {
+    top: -50000px;
+    left: -50000px;
     width: 100000px;
     height: 100000px;
 }
-.inkingCanvas-noSelect  {
+
+.inkingCanvas-noSelect {
     pointer-events: none;
     cursor: "arrow";
 }
-.inkingCanvas-paths-ink, .inkingCanvas-paths-markers {
+
+.inkingCanvas-paths-ink,
+.inkingCanvas-paths-markers {
     pointer-events: none;
     z-index: 10000; // overlays ink on top of everything 
     cursor: "arrow";
 }
+
 .inkingCanvas-paths-markers {
     mix-blend-mode: multiply;
-}
-
+}
\ No newline at end of file
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 911fc8d9c..fa9462acf 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -21,6 +21,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { SetupDrag } from '../util/DragManager';
 import { Docs } from '../documents/Documents';
 import { RouteStore } from '../../server/RouteStore';
+import { NumCast } from '../../new_fields/Types';
 
 library.add(faSearch);
 library.add(faObjectGroup);
@@ -143,18 +144,21 @@ export class SearchBox extends React.Component {
         });
         let x = 0;
         let y = 0;
+        let maxy = 300;
         for (const doc of docs) {
             doc.x = x;
             doc.y = y;
             doc.width = 200;
-            doc.height = 200;
+            doc.height = 200 * NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
+            maxy = Math.max(doc.height, maxy);
             x += 250;
             if (x > 1000) {
+                maxy = 300;
                 x = 0;
-                y += 250;
+                y += maxy + 25;
             }
         }
-        return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, title: `Search Docs: "${this.searchString}"` });
+        return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` });
     }
 
     // Useful queries:
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index f6fb79582..50201bae8 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -1,49 +1,56 @@
 .collectionPdfView-buttonTray {
-    top : 15px;
-    left : 20px;
-    position: relative; 
+    top: 15px;
+    left: 20px;
+    position: relative;
     transform-origin: left top;
     position: absolute;
 }
+
 .collectionPdfView-thumb {
-    width:25px;
-    height:25px;
+    width: 25px;
+    height: 25px;
     transform-origin: left top;
     position: absolute;
     background: darkgray;
 }
+
 .collectionPdfView-slider {
-    width:25px;
-    height:25px;
+    width: 25px;
+    height: 25px;
     transform-origin: left top;
     position: absolute;
     background: lightgray;
 }
-.collectionPdfView-cont{
+
+.collectionPdfView-cont {
     width: 100%;
     height: 100%;
-    position: absolute; 
+    position: absolute;
     top: 0;
-    left:0;
+    left: 0;
+    z-index: -1;
 }
+
 .collectionPdfView-cont-dragging {
     span {
         user-select: none;
     }
 }
+
 .collectionPdfView-backward {
-    color : white;
+    color: white;
     font-size: 24px;
-    top :0px;
-    left : 0px;
+    top: 0px;
+    left: 0px;
     position: absolute;
     background-color: rgba(50, 50, 50, 0.2);
 }
+
 .collectionPdfView-forward {
-    color : white;
+    color: white;
     font-size: 24px;
-    top :0px;
-    left : 45px;
+    top: 0px;
+    left: 45px;
     position: absolute;
     background-color: rgba(50, 50, 50, 0.2);
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7800b35df..4a21e4465 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -153,6 +153,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @undoBatch
         @action
         protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+            if (e.ctrlKey) {
+                e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
+                return;
+            }
             let html = e.dataTransfer.getData("text/html");
             let text = e.dataTransfer.getData("text/plain");
 
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 27f23a1a8..7853544d5 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,4 +1,5 @@
 import { action, observable, trace } from "mobx";
+import * as htmlToImage from "html-to-image";
 import { observer } from "mobx-react";
 import { ContextMenu } from "../ContextMenu";
 import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
@@ -6,10 +7,14 @@ import React = require("react");
 import "./CollectionVideoView.scss";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { emptyFunction } from "../../../Utils";
+import { emptyFunction, Utils } from "../../../Utils";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { VideoBox } from "../nodes/VideoBox";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, Cast, StrCast } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+import { SearchBox } from "../SearchBox";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
 
 
 @observer
@@ -67,6 +72,43 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     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
         }
+
+        let field = Cast(this.props.Document[this.props.fieldKey], VideoField);
+        if (field) {
+            let url = field.url.href;
+            ContextMenu.Instance.addItem({
+                description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt"
+            });
+        }
+        let width = NumCast(this.props.Document.width);
+        let height = NumCast(this.props.Document.height);
+        ContextMenu.Instance.addItem({
+            description: "Take Snapshot", event: async () => {
+                var canvas = document.createElement('canvas');
+                canvas.width = 640;
+                canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+                var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+                ctx && ctx.drawImage(this._videoBox!.player!, 0, 0, canvas.width, canvas.height);
+
+                //convert to desired file format
+                var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+                // if you want to preview the captured image,
+
+                let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
+                SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => {
+                    if (returnedFilename) {
+                        let url = DocServer.prepend(returnedFilename);
+                        let imageSummary = Docs.ImageDocument(url, {
+                            x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
+                            width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+                        });
+                        this.props.addDocument && this.props.addDocument(imageSummary, false);
+                        DocUtils.MakeLink(imageSummary, this.props.Document);
+                    }
+                });
+            },
+            icon: "expand-arrows-alt"
+        });
     }
 
     setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d72046800..973ebfbdd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -296,7 +296,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => {
                         if (returnedFilename) {
                             let url = DocServer.prepend(returnedFilename);
-                            let imageSummary = Docs.ImageDocument(url, { x: bounds.left, y: bounds.top + 100 / zoomBasis, width: 150, title: "-summary image-" });
+                            let imageSummary = Docs.ImageDocument(url, {
+                                x: bounds.left, y: bounds.top + 100 / zoomBasis,
+                                width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-"
+                            });
                             summary.imageSummary = imageSummary;
                             this.props.addDocument(imageSummary, false);
                         }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index aaaa6a9c5..66d91f9c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -195,6 +195,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             this.props.Document.libraryBrush = false;
         }
         let altKey = e.altKey;
+        let ctrlKey = e.ctrlKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             let isExpander = (e.target as any).id === "isExpander";
@@ -242,9 +243,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     let linkedFwdDocs = [
                         linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
                         linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
-                    if (linkedFwdDocs) {
+
+                    let linkedFwdPage = [
+                        linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined,
+                        linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined];
+                    if (!linkedFwdDocs.some(l => l instanceof Promise)) {
                         let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], altKey, document => this.props.addDocTab(document, maxLocation));
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]);
                     }
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 87c88f57c..01c4d82fb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -234,12 +234,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     makeButton = (e: React.MouseEvent): void => {
         let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
         doc.isButton = !BoolCast(doc.isButton, false);
-        if (doc.isButton && !doc.nativeWidth) {
-            doc.nativeWidth = this.props.Document[WidthSym]();
-            doc.nativeHeight = this.props.Document[HeightSym]();
-        } else {
+        if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally
+            if (doc.isButton && !doc.nativeWidth) {
+                doc.nativeWidth = this.props.Document[WidthSym]();
+                doc.nativeHeight = this.props.Document[HeightSym]();
+            } else {
 
-            doc.nativeWidth = doc.nativeHeight = undefined;
+                doc.nativeWidth = doc.nativeHeight = undefined;
+            }
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 71bda6fea..cc0dc8220 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -47,7 +47,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
-        if (this._photoIndex === 0 && (this.props as any).id !== "isExpander" && (!this.Document.nativeHeight || !this.Document.nativeWidth)) {
+        if (this._photoIndex === 0 && (this.props as any).id !== "isExpander" && (!this.Document.nativeWidth || !this.Document.nativeHeight || Math.abs(this.Document.nativeWidth / this.Document.nativeHeight - w / h) > 0.05)) {
             Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
             this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
@@ -165,8 +165,10 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     }
 
     choosePath(url: URL) {
-        if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1)
+        const lower = url.href.toLowerCase();
+        if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1 || !(lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) {
             return url.href;
+        }
         let ext = path.extname(url.href);
         return url.href.replace(ext, this._curSuffix + ext);
     }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 8cb576786..849f17aa4 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -78,7 +78,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
         let rows: JSX.Element[] = [];
         let i = 0;
-        for (let key in ids) {
+        for (let key of Object.keys(ids).sort()) {
             rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
         }
         return rows;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5b8b15ed8..733bc920f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -23,6 +23,7 @@ import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 var path = require('path');
 import React = require("react");
+import { ContextMenu } from "../ContextMenu";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
  * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, 
@@ -357,7 +358,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @computed
     get imageProxyRenderer() {
         let thumbField = this.props.Document.thumbnail;
-        if (thumbField && this._renderAsSvg) {
+        if (thumbField && this._renderAsSvg && NumCast(this.props.Document.thumbnailPage, 0) === this.Document.curPage) {
 
             // let transform = this.props.ScreenToLocalTransform().inverse();
             let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
@@ -380,10 +381,21 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
     @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true);
     @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false);
+    onContextMenu = (e: React.MouseEvent): void => {
+        let field = Cast(this.Document[this.props.fieldKey], PdfField);
+        if (field) {
+            let url = field.url.href;
+            ContextMenu.Instance.addItem({
+                description: "Copy path", event: () => {
+                    Utils.CopyText(url);
+                }, icon: "expand-arrows-alt"
+            });
+        }
+    }
     render() {
         let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
         return (
-            <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} >
+            <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} onContextMenu={this.onContextMenu} >
                 {this.pdfRenderer}
             </div >
         );
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 6ae55d151..ab57b4b04 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -88,10 +88,14 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         if (vref) {
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
             if (this._reactionDisposer) this._reactionDisposer();
-            this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
-                vref.currentTime = NumCast(this.props.Document.curPage, 0), { fireImmediately: true });
+            this._reactionDisposer = reaction(() => this.props.Document.curPage, () => {
+                if (!this.Playing) {
+                    vref.currentTime = NumCast(this.props.Document.curPage, 0);
+                }
+            }, { fireImmediately: true });
         }
     }
+
     videoContent(path: string) {
         let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : "");
         return <video className={`${style}`} ref={this.setVideoRef} onPointerDown={this.onPointerDown}>
-- 
cgit v1.2.3-70-g09d2


From 435f0c8ef035995001dde92f8e7a04fe35a3a41d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 28 May 2019 20:00:50 -0400
Subject: Added default values for Document Schemas

---
 src/client/views/nodes/DocumentView.tsx | 13 +++++++------
 src/debug/Repl.tsx                      | 11 ++++++++---
 src/new_fields/Schema.ts                | 18 +++++++++++++++---
 src/new_fields/Types.ts                 | 12 +++++++++---
 4 files changed, 39 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 01c4d82fb..2fb794925 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { createSchema, makeInterface, defaultSpec } from "../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, Utils } from "../../../Utils";
@@ -60,15 +60,15 @@ const LinkDoc = makeInterface(linkSchema);
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
-    addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
-    removeDocument?: (doc: Document) => boolean;
-    moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+    addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (doc: Doc) => boolean;
+    moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     isTopMost: boolean;
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    focus: (doc: Document) => void;
+    focus: (doc: Doc) => void;
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -81,7 +81,8 @@ const schema = createSchema({
     layout: "string",
     nativeWidth: "number",
     nativeHeight: "number",
-    backgroundColor: "string"
+    backgroundColor: "string",
+    test: defaultSpec("number", 5)
 });
 
 export const positionSchema = createSchema({
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index 01acb0e76..c2db3bdcb 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -3,6 +3,7 @@ import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
 import { observable, computed } from 'mobx';
 import { CompileScript } from '../client/util/Scripting';
+import { makeInterface } from '../new_fields/Schema';
 
 @observer
 class Repl extends React.Component {
@@ -15,12 +16,16 @@ class Repl extends React.Component {
     }
 
     onKeyDown = (e: React.KeyboardEvent) => {
-        if (e.ctrlKey && e.key === "Enter") {
-            const script = CompileScript(this.text, { addReturn: true, typecheck: false });
+        if (!e.ctrlKey && e.key === "Enter") {
+            e.preventDefault();
+            const script = CompileScript(this.text, {
+                addReturn: true, typecheck: false,
+                params: { makeInterface: "any" }
+            });
             if (!script.compiled) {
                 this.executedCommands.push({ command: this.text, result: "Compile Error" });
             } else {
-                const result = script.run();
+                const result = script.run({ makeInterface });
                 if (result.success) {
                     this.executedCommands.push({ command: this.text, result: result.result });
                 } else {
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index b821baec9..250f3c975 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,4 +1,4 @@
-import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types";
+import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types";
 import { Doc, Field } from "./Doc";
 
 type AllToInterface<T extends Interface[]> = {
@@ -10,7 +10,7 @@ export const emptySchema = createSchema({});
 export const Document = makeInterface(emptySchema);
 export type Document = makeInterface<[typeof emptySchema]>;
 
-export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc & { proto: Doc | undefined };
+export type makeInterface<T extends Interface[]> = AllToInterface<T> & Doc & { proto: Doc | undefined };
 // 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[]>(...schemas: T): (doc?: Doc) => makeInterface<T> {
@@ -24,7 +24,12 @@ export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc)
         get(target: any, prop, receiver) {
             const field = receiver.doc[prop];
             if (prop in schema) {
-                return Cast(field, (schema as any)[prop]);
+                const desc = (schema as any)[prop];
+                if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {
+                    return Cast(field, desc.type, desc.defaultVal);
+                } else {
+                    return Cast(field, (schema as any)[prop]);
+                }
             }
             return field;
         },
@@ -79,4 +84,11 @@ export function createSchema<T extends Interface>(schema: T): T & { proto: ToCon
 
 export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
     return { List: type as any };//TODO Types
+}
+
+export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
+    return {
+        type: type as any,
+        defaultVal
+    };
 }
\ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 4b4c58eb8..c04dd5e6d 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -2,12 +2,13 @@ import { Field, Opt, FieldResult, Doc } from "./Doc";
 import { List } from "./List";
 import { RefField } from "./RefField";
 
-export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
+export type ToType<T extends ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field>> =
     T extends "string" ? string :
     T extends "number" ? number :
     T extends "boolean" ? boolean :
     T extends ListSpec<infer U> ? List<U> :
     // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    T extends DefaultFieldConstructor<infer _U> ? never :
     T extends { new(...args: any[]): List<Field> } ? never :
     T extends { new(...args: any[]): infer R } ? R : never;
 
@@ -19,12 +20,17 @@ export type ToConstructor<T extends Field> =
     new (...args: any[]) => T;
 
 export type ToInterface<T extends Interface> = {
-    [P in Exclude<keyof T, "proto">]: FieldResult<ToType<T[P]>>;
+    [P in Exclude<keyof T, "proto">]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
 };
 
 // type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
 export type ListSpec<T extends Field> = { List: ToConstructor<T> };
 
+export type DefaultFieldConstructor<T extends Field> = {
+    type: ToConstructor<T>,
+    defaultVal: T
+};
+
 // type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
 
 export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
@@ -34,7 +40,7 @@ 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> | DefaultFieldConstructor<Field>;
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 
-- 
cgit v1.2.3-70-g09d2


From 732cc8c3aec072525535d246b1177181bbd3f7da Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 29 May 2019 12:06:44 -0400
Subject: improved stacking view and captions templates.

---
 src/client/documents/Documents.ts                  |   6 +-
 src/client/views/MainOverlayTextBox.tsx            |   7 +-
 src/client/views/Templates.tsx                     |   4 +-
 .../views/collections/CollectionStackingView.tsx   | 125 ++++++++++++---------
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   1 +
 src/client/views/nodes/DocumentView.tsx            |   5 +
 src/client/views/nodes/FormattedTextBox.tsx        |  21 +++-
 9 files changed, 107 insertions(+), 70 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4bc0df31f..9bf62196f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -26,7 +26,7 @@ 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";
+import { Cast, NumCast } from "../../new_fields/Types";
 import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
@@ -221,10 +221,12 @@ export namespace Docs {
         let inst = CreateInstance(imageProto, new ImageField(new URL(url)), options);
         requestImageSize(window.origin + RouteStore.corsProxy + "/" + url)
             .then((size: any) => {
+                let aspect = size.height / size.width;
                 if (!inst.proto!.nativeWidth) {
                     inst.proto!.nativeWidth = size.width;
                 }
-                inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * size.height / size.width;
+                inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect;
+                inst.proto!.height = NumCast(inst.proto!.width) * aspect;
             })
             .catch((err: any) => console.log(err));
         return inst;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 9be408049..6e8e6f8ce 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -17,6 +17,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     @observable _textXf: () => Transform = () => Transform.Identity();
     private _textFieldKey: string = "data";
     private _textColor: string | null = null;
+    private _textHideOnLeave?: boolean;
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
 
@@ -39,9 +40,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         this._textFieldKey = textFieldKey!;
         this._textXf = tx ? tx : () => Transform.Identity();
         this._textTargetDiv = div;
+        this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave;
         if (div) {
-            if (div.parentElement && div.parentElement instanceof HTMLDivElement && div.parentElement.id === "screenSpace") this._textXf = () => Transform.Identity();
-            this._textColor = div.style.color;
+            this._textColor = (getComputedStyle(div) as any).color;
             div.style.color = "transparent";
         }
     }
@@ -88,7 +89,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}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: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
-                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+                    <FormattedTextBox fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                         ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={emptyFunction} />
                 </div>
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 303f3f3b8..0cd367bcb 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -42,8 +42,8 @@ export namespace Templates {
     export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
         `<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 style="bottom: 0; font-size:14px; width:100%; position:absolute">
+                <FormattedTextBox {...props} fieldKey={"caption"} hideOnLeave={"true"} />
             </div>
         </div>` );
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a29648d5b..425eecebb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,9 +4,9 @@ import { CollectionSubView, CollectionViewProps, SubCollectionViewProps } from "
 import { Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
 import { DocumentView } from "../nodes/DocumentView";
 import { Transform } from "../../util/Transform";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne, Utils } from "../../../Utils";
 import "./CollectionStackingView.scss";
-import { action, reaction } from "mobx";
+import { action, reaction, trace, computed } from "mobx";
 import { StrCast, NumCast } from "../../../new_fields/Types";
 import { Id } from "../../../new_fields/FieldSymbols";
 
@@ -14,20 +14,13 @@ import { Id } from "../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
-    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform();
+
+    get gridGap() { return 10; }
+    get gridSize() { return 20; }
+    get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
     constructor(props: SubCollectionViewProps) {
         super(props);
-        // reaction(() => [this.props.PanelHeight() + this.props.PanelWidth(),
-        // (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document[this.props.ContainingCollectionView.props.fieldKey])], () => {
-        //         if (this.props.ContainingCollectionView) {
-        //             let allItems = DocListCast(this.props.ContainingCollectionView.props.Document[this.props.ContainingCollectionView.props.fieldKey]);
-        //             for (let x = 0; x < allItems.length; x++) {
-        //                 resizeGridItem(allItems[x]);
-        //             }
-        //         }
-        //     }
-        // );
     }
 
     @action
@@ -36,57 +29,79 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         addDocument(doc);
         return true;
     }
+    getDocTransform(doc: Doc, dref: HTMLDivElement) {
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+        let outerXf = Utils.GetScreenTransform(this.masonryGridRef!);
+        let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+        return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.itemWidth);
+    }
+    masonryGridRef: HTMLDivElement | null = null;
+    createRef = (ele: HTMLDivElement | null) => {
+        this.masonryGridRef = ele;
+        this.createDropTarget(ele!);
+    }
+    @computed
+    get children() {
+        return this.childDocs.map(d => {
+            let colSpan = Math.ceil((this.itemWidth + this.gridGap) / (this.gridSize + this.gridGap));
+            let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
+            let dref = React.createRef<HTMLDivElement>();
+            let dxf = () => this.getDocTransform(d, dref.current!);
+            return (<div className="colletionStackingView-masonryDoc"
+                key={d[Id]}
+                ref={dref}
+                style={{
+                    transformOrigin: "top left",
+                    gridRowEnd: `span ${rowSpan}`,
+                    gridColumnEnd: `span ${colSpan}`,
+                    transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, 1)}, ${this.itemWidth / NumCast(d.nativeWidth, 1)})`
+                }} >
+                <DocumentView key={d[Id]} Document={d}
+                    addDocument={this.props.addDocument}
+                    removeDocument={this.props.removeDocument}
+                    moveDocument={this.moveDocument}
+                    ContainingCollectionView={this.props.CollectionView}
+                    isTopMost={false}
+                    ScreenToLocalTransform={dxf}
+                    focus={emptyFunction}
+                    ContentScaling={returnOne}
+                    PanelWidth={d[WidthSym]}
+                    PanelHeight={d[HeightSym]}
+                    selectOnLoad={false}
+                    parentActive={this.props.active}
+                    addDocTab={this.props.addDocTab}
+                    bringToFront={emptyFunction}
+                    toggleMinimized={emptyFunction}
+                    whenActiveChanged={this.props.whenActiveChanged} />
+            </div>);
+        })
+    }
+    onClick = (e: React.MouseEvent) => {
+        if (this.props.active()) {
+            let rect = (this.masonryGridRef!.firstChild! as HTMLElement).getBoundingClientRect();
+            if (e.clientX < rect.left || e.clientX > rect.right || e.clientY > rect.bottom) this.props.select(false);
+            e.stopPropagation();
+        }
+    }
     render() {
-        const docs = this.childDocs;
-        let gridGap = 10;
-        let gridSize = 20;
-        let itemWidth = NumCast(this.props.Document.itemWidth, 250);
         let leftMargin = 20;
         let topMargin = 20;
-        let itemCols = Math.ceil(itemWidth / (gridSize + gridGap));
-        let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (gridSize + gridGap)));
+        let itemCols = Math.ceil(this.itemWidth / (this.gridSize + this.gridGap));
+        let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
         return (
-            <div className="collectionStackingView" ref={this.createDropTarget} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
+            <div className="collectionStackingView" ref={this.createRef} onClick={this.onClick} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
                 <div className="collectionStackingview-masonryGrid"
                     style={{
                         padding: `${topMargin}px 0px 0px ${leftMargin}px`,
-                        width: `${cells * itemCols * (gridSize + gridGap) + leftMargin}`,
-                        margin: "auto", position: "relative",
-                        gridGap: gridGap,
-                        gridTemplateColumns: `repeat(auto-fill, minmax(${gridSize}px,1fr))`,
-                        gridAutoRows: `${gridSize}px`
+                        width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
+                        height: "auto",
+                        marginLeft: "auto", marginRight: "auto", position: "relative",
+                        gridGap: this.gridGap,
+                        gridTemplateColumns: `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`,
+                        gridAutoRows: `${this.gridSize}px`
                     }}
                 >
-                    {docs.map(d => {
-                        let colSpan = Math.ceil((itemWidth + gridGap) / (gridSize + gridGap));
-                        let rowSpan = Math.ceil((itemWidth / d[WidthSym]() * d[HeightSym]() + gridGap) / (gridSize + gridGap));
-                        return (<div className="mycontent" id={StrCast(d.title, "")}
-                            key={d[Id]}
-                            style={{
-                                transformOrigin: "top left",
-                                gridRowEnd: `span  ${rowSpan}`,
-                                gridColumnEnd: `span  ${colSpan}`,
-                                transform: `scale(${itemWidth / NumCast(d.nativeWidth, 1)}, ${itemWidth / NumCast(d.nativeWidth, 1)})`
-                            }} >
-                            <DocumentView Document={d}
-                                addDocument={this.props.addDocument}
-                                removeDocument={this.props.removeDocument}
-                                moveDocument={this.moveDocument}
-                                ContainingCollectionView={this.props.CollectionView}
-                                isTopMost={false}
-                                ScreenToLocalTransform={this.getPreviewTransform}
-                                focus={emptyFunction}
-                                ContentScaling={returnOne}
-                                PanelWidth={d[WidthSym]}
-                                PanelHeight={d[HeightSym]}
-                                selectOnLoad={false}
-                                parentActive={this.props.active}
-                                addDocTab={this.props.addDocTab}
-                                bringToFront={emptyFunction}
-                                toggleMinimized={emptyFunction}
-                                whenActiveChanged={this.props.active} />
-                        </div>);
-                    })}
+                    {this.children}
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c4e72b23a..fe9e12640 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -212,7 +212,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     }).then(async (res: Response) => {
                         (await res.json()).map(action((file: any) => {
                             let path = window.location.origin + file;
-                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
+                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
 
                             docPromise.then(doc => doc && this.props.addDocument(doc));
                         }));
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 66d91f9c2..04e0a91f8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -256,9 +256,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
     }
 
-    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
-    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
-
     borderRounding = () => {
         let br = NumCast(this.props.Document.borderRounding);
         return br >= 0 ? br :
@@ -283,8 +280,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-                onPointerDown={this.onPointerDown}
-                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
+                onPointerDown={this.onPointerDown} onPointerOver={this.onPointerEnter}
                 onClick={this.onClick}
                 style={{
                     outlineColor: "maroon",
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 52a9582d2..02396c3af 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -44,6 +44,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
     layoutKey: string,
+    hideOnLeave?: boolean
 }> {
     @computed get layout(): string {
         const layout = Cast(this.props.Document[this.props.layoutKey], "string");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 01c4d82fb..ed5862660 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -343,6 +343,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
+
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
 
@@ -366,6 +369,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     transform: `scale(${scaling}, ${scaling})`
                 }}
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
             >
                 {this.contents}
             </div>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5afef221c..5b74c2e7e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -50,8 +50,9 @@ library.add(faSmile);
 //  this will edit the document and assign the new value to that field.
 //]
 
-export interface FormattedTextBoxOverlay {
+export interface FormattedTextBoxProps {
     isOverlay?: boolean;
+    hideOnLeave?: boolean;
 }
 
 const richTextSchema = createSchema({
@@ -62,7 +63,7 @@ type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
 
 @observer
-export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
     public static LayoutString(fieldStr: string = "data") {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -357,6 +358,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this._undoTyping = UndoManager.StartBatch("undoTyping");
         }
     }
+
+    @observable
+    _entered = false;
+    @action
+    onPointerEnter = (e: React.PointerEvent) => {
+        this._entered = true;
+    }
+    @action
+    onPointerLeave = (e: React.PointerEvent) => {
+        this._entered = false;
+    }
     render() {
         let style = this.props.isOverlay ? "scroll" : "hidden";
         let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
@@ -364,6 +376,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return (
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
                 style={{
+                    background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined,
+                    opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
+                    color: this.props.hideOnLeave ? "white" : "initial",
                     pointerEvents: interactive ? "all" : "none",
                 }}
                 // onKeyDown={this.onKeyPress}
@@ -377,6 +392,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onContextMenu={this.specificContextMenu}
                 // tfs: do we need this event handler
                 onWheel={this.onPointerWheel}
+                onPointerEnter={this.onPointerEnter}
+                onPointerLeave={this.onPointerLeave}
             >
                 <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} />
             </div>
-- 
cgit v1.2.3-70-g09d2


From 9dbf61fccc96f7c4d6bd63e25a7208b82df28705 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 29 May 2019 18:21:03 -0400
Subject: stacking view focus fixes.  linkview fixes.-

---
 src/client/documents/Documents.ts                      |  3 ++-
 .../views/collections/CollectionStackingView.scss      |  2 +-
 .../views/collections/CollectionStackingView.tsx       | 10 ++++++++--
 src/client/views/collections/CollectionView.tsx        | 13 +++++++++++++
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx | 18 +++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx     |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx     |  9 +--------
 src/client/views/nodes/DocumentView.tsx                |  6 ++++++
 src/client/views/nodes/FormattedTextBox.tsx            |  1 -
 9 files changed, 43 insertions(+), 21 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9bf62196f..fffada459 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -36,6 +36,7 @@ import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
 import { RouteStore } from "../../server/RouteStore";
 var requestImageSize = require('request-image-size');
+var path = require('path');
 
 export interface DocumentOptions {
     x?: number;
@@ -218,7 +219,7 @@ export namespace Docs {
     }
 
     export function ImageDocument(url: string, options: DocumentOptions = {}) {
-        let inst = CreateInstance(imageProto, new ImageField(new URL(url)), options);
+        let inst = CreateInstance(imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options });
         requestImageSize(window.origin + RouteStore.corsProxy + "/" + url)
             .then((size: any) => {
                 let aspect = size.height / size.width;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 6043a813d..0e4a8bb7e 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -8,7 +8,7 @@
     width: 100%;
     height: 100%;
     position: absolute;
-    overflow-y: scroll;
+    overflow-y: auto;
     min-width: 250px;
     border-width: 0;
     box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 425eecebb..6d44aa37d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -47,14 +47,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
             let dref = React.createRef<HTMLDivElement>();
             let dxf = () => this.getDocTransform(d, dref.current!);
+            let childFocus = (doc: Doc) => {
+                doc.libraryBrush = true;
+                this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered.
+            }
             return (<div className="colletionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
                 style={{
+                    width: NumCast(d.nativeWidth, d[WidthSym]()),
+                    height: NumCast(d.nativeHeight, d[HeightSym]()),
                     transformOrigin: "top left",
                     gridRowEnd: `span ${rowSpan}`,
                     gridColumnEnd: `span ${colSpan}`,
-                    transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, 1)}, ${this.itemWidth / NumCast(d.nativeWidth, 1)})`
+                    transform: `scale(${this.itemWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.itemWidth / NumCast(d.nativeWidth, d[WidthSym]())})`
                 }} >
                 <DocumentView key={d[Id]} Document={d}
                     addDocument={this.props.addDocument}
@@ -63,7 +69,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ContainingCollectionView={this.props.CollectionView}
                     isTopMost={false}
                     ScreenToLocalTransform={dxf}
-                    focus={emptyFunction}
+                    focus={childFocus}
                     ContentScaling={returnOne}
                     PanelWidth={d[WidthSym]}
                     PanelHeight={d[HeightSym]}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 39c8af4ab..a1e7c7c69 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,6 +13,8 @@ import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackingView } from './CollectionStackingView';
+import { NumCast } from '../../../new_fields/Types';
+import { WidthSym, HeightSym } from '../../../new_fields/Doc';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -42,6 +44,16 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
 
+    freezeNativeDimensions = (e: React.MouseEvent): void => {
+        if (NumCast(this.props.Document.nativeWidth)) {
+            this.props.Document.proto!.nativeWidth = undefined;
+            this.props.Document.proto!.nativeHeight = undefined;
+
+        } else {
+            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+        }
+    }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !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.viewType = CollectionViewType.Freeform), icon: "signature" });
@@ -51,6 +63,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
             ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
             ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
             ContextMenu.Instance.addItem({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
+            ContextMenu.Instance.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 85556fb90..6207dc8af 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -95,22 +95,26 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     @computed
     get uniqueConnections() {
+        console.log("-----");
         let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
             let possiblePairs: { a: Doc, b: Doc, }[] = [];
             srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
-            possiblePairs.map(possiblePair =>
-                drawnPairs.reduce((found, drawnPair) => {
-                    let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
+            possiblePairs.map(possiblePair => {
+                if (!drawnPairs.reduce((found, drawnPair) => {
+                    let match1 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.a) && Doc.AreProtosEqual(possiblePair.b, drawnPair.b));
+                    let match2 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.b) && Doc.AreProtosEqual(possiblePair.b, drawnPair.a));
+                    let match = match1 || match2;
                     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)) {
+                    console.log("A" + possiblePair.a[Id] + " B" + possiblePair.b[Id] + " L" + connection.l[Id]);
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+                }
+            });
             return drawnPairs;
         }, [] as { a: Doc, b: Doc, l: Doc[] }[]);
         return connections.map(c => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 64d5301c9..14dbe1899 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -87,7 +87,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             navigator.clipboard.readText().then(text => {
                 let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
                 if (ns.length === 1 && text.startsWith("http")) {
-                    this.props.addDocument(Docs.ImageDocument(text, { width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
+                    this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
                 } else {
                     this.pasteTable(ns, x, y);
                 }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 04e0a91f8..5f4d9e9ec 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -280,16 +280,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-                onPointerDown={this.onPointerDown} onPointerOver={this.onPointerEnter}
+                onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
                 style={{
-                    outlineColor: "maroon",
-                    outlineStyle: "dashed",
-                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
-                        BoolCast(this.props.Document.protoBrush, false) ?
-                        `${1 * this.getTransform().Scale}px`
-                        // "2px"
-                        : "0px",
                     opacity: zoomFade,
                     borderRadius: `${this.borderRounding()}px`,
                     transformOrigin: "left top",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ed5862660..98f4f050a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -362,6 +362,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
+                    outlineColor: "maroon",
+                    outlineStyle: "dashed",
+                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
+                        BoolCast(this.props.Document.protoBrush, false) ?
+                        `${1 * this.props.ScreenToLocalTransform().Scale}px`
+                        : "0px",
                     borderRadius: "inherit",
                     background: this.Document.backgroundColor || "",
                     width: nativeWidth,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5b74c2e7e..da584c811 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -275,7 +275,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
     freezeNativeDimensions = (e: React.MouseEvent): void => {
         if (NumCast(this.props.Document.nativeWidth)) {
             this.props.Document.proto!.nativeWidth = undefined;
-- 
cgit v1.2.3-70-g09d2


From 83edfcd06b659839f161121728de02aca91d4af8 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 30 May 2019 00:20:35 -0400
Subject: added image rotation.  added double click on stacking views.  need to
 cleanup/fix DocumentView click events

---
 .../views/collections/CollectionStackingView.scss   |  2 +-
 .../views/collections/CollectionStackingView.tsx    | 15 +++++++++++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx  | 21 +--------------------
 src/client/views/nodes/DocumentView.tsx             | 12 +++++++++++-
 src/client/views/nodes/ImageBox.tsx                 | 19 +++++++++++++++++--
 5 files changed, 41 insertions(+), 28 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 0e4a8bb7e..4d240342c 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -31,7 +31,7 @@
         align-items: center;
     }
 
-    .collectionStackingview-masonryGrid {
+    .collectionStackingView-masonryGrid {
         display:grid;
         width:100%;
         height:100%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6d44aa37d..943e8dd5f 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -83,10 +83,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         })
     }
     onClick = (e: React.MouseEvent) => {
+        let hitBackground = (e.target as any).className === "collectionStackingView-masonryGrid" ||
+            (e.target as any).className === "collectionStackingView";
         if (this.props.active()) {
-            let rect = (this.masonryGridRef!.firstChild! as HTMLElement).getBoundingClientRect();
-            if (e.clientX < rect.left || e.clientX > rect.right || e.clientY > rect.bottom) this.props.select(false);
-            e.stopPropagation();
+            if (!hitBackground)
+                e.stopPropagation();
+        }
+        if (hitBackground) {
+            if (!this.props.active() && this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.active()) {
+                e.stopPropagation();
+            }
+            this.props.select(false);
         }
     }
     render() {
@@ -96,7 +103,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
         return (
             <div className="collectionStackingView" ref={this.createRef} onClick={this.onClick} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
-                <div className="collectionStackingview-masonryGrid"
+                <div className="collectionStackingView-masonryGrid"
                     style={{
                         padding: `${topMargin}px 0px 0px ${leftMargin}px`,
                         width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5f4d9e9ec..5d11e051d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -33,7 +33,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     private _downX: number = 0;
     private _downY: number = 0;
     private _doubleTap = false;
-    private _lastTap: number = 0;
     _bringToFrontDisposer?: IReactionDisposer;
 
     @computed get transform() {
@@ -169,31 +168,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (e.button === 0 && e.altKey) {
             e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
         } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
-        if (Date.now() - this._lastTap < 300) {
-            if (e.buttons === 1) {
-                document.removeEventListener("pointerup", this.onPointerUp);
-                document.addEventListener("pointerup", this.onPointerUp);
-            }
-        } else {
-            this._lastTap = Date.now();
-        }
+
         if (e.button === 0) {
             e.preventDefault();  // prevents Firefox from dragging images (we want to do that ourself)
         }
     }
-    onPointerUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
-            this._doubleTap = true;
-        }
-    }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
-        if (this._doubleTap) {
-            this.props.addDocTab(this.props.Document, "inTab");
-            SelectionManager.DeselectAll();
-            this.props.Document.libraryBrush = false;
-        }
         let altKey = e.altKey;
         let ctrlKey = e.ctrlKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 98f4f050a..36c14fbf2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -183,13 +183,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
+    _doubleTap = false;
     onClick = (e: React.MouseEvent): void => {
-        if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
+        if (this._doubleTap && !this.props.isTopMost) {
+            this.props.addDocTab(this.props.Document, "inTab");
+            SelectionManager.DeselectAll();
+            this.props.Document.libraryBrush = false;
+            e.stopPropagation();
+        }
+        else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
                 Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
     }
+    private _lastTap: number = 0;
     _hitExpander = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
@@ -222,6 +230,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
+        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+        this._lastTap = Date.now();
     }
 
     deleteClicked = (): void => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d70068295..8ea6c5436 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -12,11 +12,11 @@ import React = require("react");
 import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
 import { DocComponent } from '../DocComponent';
 import { positionSchema } from './DocumentView';
-import { FieldValue, Cast, StrCast, PromiseValue } from '../../../new_fields/Types';
+import { FieldValue, Cast, StrCast, PromiseValue, NumCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { List } from '../../../new_fields/List';
 import { InkingControl } from '../InkingControl';
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc';
 import { faImage } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
 var path = require('path');
@@ -136,6 +136,17 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                     Utils.CopyText(url);
                 }, icon: "expand-arrows-alt"
             });
+            ContextMenu.Instance.addItem({
+                description: "Rotate", event: action(() => {
+                    this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360;
+                    let nw = this.props.Document.nativeWidth;
+                    this.props.Document.nativeWidth = this.props.Document.nativeHeight;
+                    this.props.Document.nativeHeight = nw;
+                    let w = this.props.Document.width;
+                    this.props.Document.width = this.props.Document.height;
+                    this.props.Document.height = w;
+                }), icon: "expand-arrows-alt"
+            });
         }
     }
 
@@ -199,6 +210,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => this.choosePath((p as ImageField).url));
         // }
         let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
+        let rotation = NumCast(this.props.Document.rotation, 0);
+        let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1;
+        let shift = (rotation % 180) ? (this.props.Document[HeightSym]() - this.props.Document[WidthSym]() / aspect) / 2 : 0;
         return (
             <div id={id} className={`imageBox-cont${interactive}`}
                 onPointerDown={this.onPointerDown}
@@ -206,6 +220,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 <img id={id}
                     key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
                     src={paths[Math.min(paths.length, this._photoIndex)]}
+                    style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
                     // style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }}
                     width={nativeWidth}
                     ref={this._imgRef}
-- 
cgit v1.2.3-70-g09d2


From 37e16bb59a38c000ba80312a0661e1f54c93f3c6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 30 May 2019 10:51:00 -0400
Subject: cleaned up event handling for documentviews.   fixed context menu
 closing.

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/views/ContextMenu.tsx                   |   7 +-
 src/client/views/ContextMenuItem.tsx               |  34 +++--
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/MainView.tsx                      |   1 -
 .../views/collections/CollectionBaseView.tsx       |   1 +
 .../views/collections/CollectionDockingView.tsx    |   1 -
 .../views/collections/CollectionSchemaView.tsx     |   1 -
 .../views/collections/CollectionStackingView.tsx   |  28 +----
 .../views/collections/CollectionTreeView.tsx       |  22 ++--
 .../CollectionFreeFormLinksView.tsx                |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 119 ------------------
 src/client/views/nodes/DocumentView.tsx            | 140 ++++++++++++++++-----
 src/client/views/nodes/FieldView.tsx               |   1 -
 15 files changed, 153 insertions(+), 208 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fffada459..ab61b915c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -295,7 +295,7 @@ export namespace Docs {
         return CreateInstance(webProto, new HtmlField(html), options);
     }
     export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
-        return CreateInstance(kvpProto, document, options);
+        return CreateInstance(kvpProto, document, { title: document.title + ".kvp", ...options });
     }
     export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
         if (!makePrototype) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 542d259d6..da374455e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -81,6 +81,11 @@ export class ContextMenu extends React.Component {
         return false;
     }
 
+    @action
+    closeMenu = () => {
+        this.clearItems();
+    }
+
     render() {
         let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
             { left: this._pageX, bottom: this._pageY, display: this._display };
@@ -95,7 +100,7 @@ export class ContextMenu extends React.Component {
                     <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} />
                 </span>
                 {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
-                    map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
+                    map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.closeMenu} />)}
             </div>
         );
     }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index b9239dac9..63347e076 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -8,11 +8,13 @@ export interface OriginalMenuProps {
     description: string;
     event: (e: React.MouseEvent<HTMLDivElement>) => void;
     icon?: IconProp; //maybe should be optional (icon?)
+    closeMenu?: () => void;
 }
 
 export interface SubmenuProps {
     description: string;
     subitems: ContextMenuProps[];
+    closeMenu?: () => void;
 }
 
 export interface ContextMenuItemProps {
@@ -32,30 +34,36 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
         }
     }
 
+    handleEvent = (e: React.MouseEvent<HTMLDivElement>) => {
+        if ("event" in this.props) {
+            this.props.event(e);
+            this.props.closeMenu && this.props.closeMenu();
+        }
+    }
+
     render() {
         if ("event" in this.props) {
             return (
-                <div className="contextMenu-item" onClick={this.props.event}>
+                <div className="contextMenu-item" onClick={this.handleEvent}>
                     <span className="icon-background">
                         {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : <FontAwesomeIcon icon="circle" size="sm" />}
                     </span>
-                    <div className="contextMenu-description"> {this.props.description}</div>
+                    <div className="contextMenu-description">
+                        {this.props.description}
+                    </div>
                 </div>
             );
         }
         else {
-            let submenu = null;
-            if (this.overItem) {
-                submenu = (
-                    <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
-                        {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
-                    </div>
-                );
-            }
+            let submenu = !this.overItem ? (null) :
+                <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
+                </div>;
             return (
-                <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })}
-                    onMouseLeave={action(() => this.overItem = false)}>
-                    <div className="contextMenu-description"> {this.props.description}</div>
+                <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })} onMouseLeave={action(() => this.overItem = false)}>
+                    <div className="contextMenu-description">
+                        {this.props.description}
+                    </div>
                     {submenu}
                 </div>
             );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 319e91337..e06fd6119 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -251,7 +251,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }
             if (!this._removeIcon) {
                 if (selectedDocs.length === 1) {
-                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].toggleMinimized());
                 } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
                     Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
                     let docViews = SelectionManager.ViewsSortedVertically();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 438607157..74dafadc4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -184,7 +184,6 @@ export class MainView extends React.Component {
         let mainCont = this.mainContainer;
         let content = !mainCont ? (null) :
             <DocumentView Document={mainCont}
-                toggleMinimized={emptyFunction}
                 addDocument={undefined}
                 addDocTab={emptyFunction}
                 removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 2bccde241..e89b8be0f 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -128,6 +128,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     removeDocument(doc: Doc): boolean {
+        SelectionManager.DeselectAll();
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
         const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 180a8be46..b6076b5f7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -466,7 +466,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
                 <DocumentView key={this._document[Id]} Document={this._document}
-                    toggleMinimized={emptyFunction}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 1af13765b..b17d5b865 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -412,7 +412,6 @@ class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewPro
             {!this.props.Document || !this.props.width ? (null) : (
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)` }}>
                     <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
-                        toggleMinimized={emptyFunction}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
                         ScreenToLocalTransform={this.getTransform}
                         ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 943e8dd5f..bf246d4ec 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -14,15 +14,11 @@ import { Id } from "../../../new_fields/FieldSymbols";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
-
+    _masonryGridRef: HTMLDivElement | null = null;
     get gridGap() { return 10; }
     get gridSize() { return 20; }
     get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
-    constructor(props: SubCollectionViewProps) {
-        super(props);
-    }
-
     @action
     moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
         this.props.removeDocument(doc);
@@ -31,13 +27,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocTransform(doc: Doc, dref: HTMLDivElement) {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
-        let outerXf = Utils.GetScreenTransform(this.masonryGridRef!);
+        let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
         let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
         return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.itemWidth);
     }
-    masonryGridRef: HTMLDivElement | null = null;
     createRef = (ele: HTMLDivElement | null) => {
-        this.masonryGridRef = ele;
+        this._masonryGridRef = ele;
         this.createDropTarget(ele!);
     }
     @computed
@@ -77,32 +72,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     parentActive={this.props.active}
                     addDocTab={this.props.addDocTab}
                     bringToFront={emptyFunction}
-                    toggleMinimized={emptyFunction}
                     whenActiveChanged={this.props.whenActiveChanged} />
             </div>);
         })
     }
-    onClick = (e: React.MouseEvent) => {
-        let hitBackground = (e.target as any).className === "collectionStackingView-masonryGrid" ||
-            (e.target as any).className === "collectionStackingView";
-        if (this.props.active()) {
-            if (!hitBackground)
-                e.stopPropagation();
-        }
-        if (hitBackground) {
-            if (!this.props.active() && this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.active()) {
-                e.stopPropagation();
-            }
-            this.props.select(false);
-        }
-    }
     render() {
         let leftMargin = 20;
         let topMargin = 20;
         let itemCols = Math.ceil(this.itemWidth / (this.gridSize + this.gridGap));
         let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
         return (
-            <div className="collectionStackingView" ref={this.createRef} onClick={this.onClick} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
+            <div className="collectionStackingView" ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
                 <div className="collectionStackingView-masonryGrid"
                     style={{
                         padding: `${topMargin}px 0px 0px ${leftMargin}px`,
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8ad495762..85aab96d3 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -26,6 +26,7 @@ export interface TreeViewProps {
     deleteDoc: (doc: Doc) => void;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
+    addDocTab: (doc: Doc, where: string) => void;
 }
 
 export enum BulletType {
@@ -140,19 +141,12 @@ class TreeView extends React.Component<TreeViewProps> {
     onWorkspaceContextMenu = (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: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
-            ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
-            ContextMenu.Instance.addItem({
-                description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
-                    { title: this.props.document.title + ".kvp", width: 300, height: 300 }))
-            });
+            ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight") });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight") });
             if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
                 ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
             }
-            ContextMenu.Instance.addItem({
-                description: "Delete", event: undoBatch(() => {
-                    this.props.deleteDoc(this.props.document);
-                })
-            });
+            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             e.stopPropagation();
         }
@@ -180,7 +174,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         {(key === "data") ? (null) :
                             <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>}
                         <div style={{ display: "block", marginTop: `${spacing}px` }}>
-                            {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+                            {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction, this.props.addDocTab)}
                         </div>
                     </ul >);
                 } else {
@@ -197,9 +191,9 @@ class TreeView extends React.Component<TreeViewProps> {
             </li>
         </div>;
     }
-    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) {
         return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
-            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} />);
     }
 }
 
@@ -225,7 +219,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         if (!this.childDocs) {
             return (null);
         }
-        let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction);
+        let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab);
 
         return (
             <div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 6207dc8af..a43c5f241 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -95,7 +95,6 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
 
     @computed
     get uniqueConnections() {
-        console.log("-----");
         let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d1c4fb72d..1a1614ac7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -279,7 +279,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getDocumentViewProps(document: Doc): DocumentViewProps {
         return {
             Document: document,
-            toggleMinimized: emptyFunction,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5d11e051d..411e8c059 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -30,9 +30,6 @@ const FreeformDocument = makeInterface(schema, positionSchema);
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
     private _mainCont = React.createRef<HTMLDivElement>();
-    private _downX: number = 0;
-    private _downY: number = 0;
-    private _doubleTap = false;
     _bringToFrontDisposer?: IReactionDisposer;
 
     @computed get transform() {
@@ -62,7 +59,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
-    toggleMinimized = async () => this.toggleIcon(await DocListCastAsync(this.props.Document.maximizedDocs));
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@@ -70,7 +66,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed
     get docView() {
         return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
-            toggleMinimized={this.toggleMinimized}
             ContentScaling={this.contentScaling}
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
@@ -124,118 +119,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         },
             2);
     }
-    @action
-    public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
-        SelectionManager.DeselectAll();
-        let isMinimized: boolean | undefined;
-        let minimizedDoc: Doc | undefined = this.props.Document;
-        if (!maximizedDocs) {
-            minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
-            if (minimizedDoc) maximizedDocs = await DocListCastAsync(minimizedDoc.maximizedDocs);
-        }
-        if (minimizedDoc && maximizedDocs) {
-            let minimizedTarget = minimizedDoc;
-            if (!CollectionFreeFormDocumentView._undoBatch) {
-                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
-            }
-            maximizedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
-                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
-                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
-                    if (isMinimized === undefined) {
-                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
-                    }
-                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
-                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
-                    if (minx !== undefined && miny !== undefined) {
-                        let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
-                        maximizedDoc.willMaximize = isMinimized;
-                        maximizedDoc.isMinimized = false;
-                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
-                    }
-                }
-            });
-            setTimeout(() => {
-                CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
-                CollectionFreeFormDocumentView._undoBatch = undefined;
-            }, 500);
-        }
-    }
-    static _undoBatch?: UndoManager.Batch = undefined;
-    onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this._doubleTap = false;
-        if (e.button === 0 && e.altKey) {
-            e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
-        } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
-
-        if (e.button === 0) {
-            e.preventDefault();  // prevents Firefox from dragging images (we want to do that ourself)
-        }
-    }
-    onClick = async (e: React.MouseEvent) => {
-        e.stopPropagation();
-        let altKey = e.altKey;
-        let ctrlKey = e.ctrlKey;
-        if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
-            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            let isExpander = (e.target as any).id === "isExpander";
-            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
-                SelectionManager.DeselectAll();
-                let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
-                let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
-                let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
-                let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
-                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
-                let expandedDocs: Doc[] = [];
-                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
-                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
-                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
-                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
-                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
-                    let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc));
-                    let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
-                    let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
-                    if (altKey) {
-                        maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace");
-                        if (!maxLocation || maxLocation === "inPlace") {
-                            let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
-                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
-                            expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
-                            let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
-                            if (!hasView) {
-                                this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false));
-                            }
-                            expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
-                        }
-                    }
-                    if (maxLocation && maxLocation !== "inPlace") {
-                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
-                        if (dataDocs) {
-                            expandedDocs.forEach(maxDoc =>
-                                (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
-                                    this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
-                        }
-                    } else {
-                        this.toggleIcon(expandedProtoDocs);
-                    }
-                }
-                else if (linkedToDocs.length || linkedFromDocs.length) {
-                    let linkedFwdDocs = [
-                        linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
-                        linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
-
-                    let linkedFwdPage = [
-                        linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined,
-                        linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined];
-                    if (!linkedFwdDocs.some(l => l instanceof Promise)) {
-                        let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]);
-                    }
-                }
-            }
-        }
-    }
 
     borderRounding = () => {
         let br = NumCast(this.props.Document.borderRounding);
@@ -261,8 +144,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
-                onPointerDown={this.onPointerDown}
-                onClick={this.onClick}
                 style={{
                     opacity: zoomFade,
                     borderRadius: `${this.borderRounding()}px`,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 36c14fbf2..a16a52ac6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -2,11 +2,11 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, StrCast, NumCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
@@ -16,7 +16,7 @@ import { DragManager, dropActionType } from "../../util/DragManager";
 import { SearchUtil } from "../../util/SearchUtil";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
@@ -72,9 +72,9 @@ export interface DocumentViewProps {
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-    toggleMinimized: () => void;
     bringToFront: (doc: Doc) => void;
     addDocTab: (doc: Doc, where: string) => void;
+    whenClicked?: () => void;
 }
 
 const schema = createSchema({
@@ -103,6 +103,9 @@ const Document = makeInterface(schema);
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
     private _downX: number = 0;
     private _downY: number = 0;
+    private _lastTap: number = 0;
+    private _doubleTap = false;
+    private _hitExpander = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
 
@@ -182,23 +185,112 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
     }
+    toggleMinimized = async () => {
+        let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
+        if (minimizedDoc) {
+            let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(
+                NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
+            this.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
+        }
+    }
 
-    _doubleTap = false;
-    onClick = (e: React.MouseEvent): void => {
+    static _undoBatch?: UndoManager.Batch = undefined;
+    @action
+    public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
+        SelectionManager.DeselectAll();
+        if (expandedDocs) {
+            if (!DocumentView._undoBatch) {
+                DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            }
+            let isMinimized: boolean | undefined;
+            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
+                    if (isMinimized === undefined) {
+                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                    }
+                    maximizedDoc.willMaximize = isMinimized;
+                    maximizedDoc.isMinimized = false;
+                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
+                }
+            });
+            setTimeout(() => {
+                DocumentView._undoBatch && DocumentView._undoBatch.end();
+                DocumentView._undoBatch = undefined;
+            }, 500);
+        }
+    }
+    onClick = async (e: React.MouseEvent) => {
+        e.stopPropagation();
+        let altKey = e.altKey;
+        let ctrlKey = e.ctrlKey;
         if (this._doubleTap && !this.props.isTopMost) {
             this.props.addDocTab(this.props.Document, "inTab");
             SelectionManager.DeselectAll();
             this.props.Document.libraryBrush = false;
-            e.stopPropagation();
         }
         else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
                 Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             SelectionManager.SelectDoc(this, e.ctrlKey);
+            let isExpander = (e.target as any).id === "isExpander";
+            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+                SelectionManager.DeselectAll();
+                let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
+                let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
+                let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
+                let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
+                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+                let expandedDocs: Doc[] = [];
+                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
+                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
+                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
+                    let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc));
+                    let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
+                    let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
+                    if (altKey) {
+                        maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace");
+                        if (!maxLocation || maxLocation === "inPlace") {
+                            let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
+                            expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
+                            let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+                            if (!hasView) {
+                                this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false));
+                            }
+                            expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
+                        }
+                    }
+                    if (maxLocation && maxLocation !== "inPlace") {
+                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
+                        if (dataDocs) {
+                            expandedDocs.forEach(maxDoc =>
+                                (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
+                                    this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
+                        }
+                    } else {
+                        let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
+                        this.collapseToPoint(scrpt, expandedProtoDocs);
+                    }
+                }
+                else if (linkedToDocs.length || linkedFromDocs.length) {
+                    let linkedFwdDocs = [
+                        linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
+                        linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
+
+                    let linkedFwdPage = [
+                        linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined,
+                        linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined];
+                    if (!linkedFwdDocs.some(l => l instanceof Promise)) {
+                        let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]);
+                    }
+                }
+            }
         }
     }
-    private _lastTap: number = 0;
-    _hitExpander = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -206,8 +298,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.shiftKey && e.buttons === 1) {
             CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
-        } else if (this.active) {
-            //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc
+        } else {
+            if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -215,7 +307,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble) {
+        if (!e.cancelBubble && this.active) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
@@ -234,32 +326,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._lastTap = Date.now();
     }
 
-    deleteClicked = (): void => {
-        this.props.removeDocument && this.props.removeDocument(this.props.Document);
-    }
-    fieldsClicked = (e: React.MouseEvent): void => {
-        let kvp = Docs.KVPDocument(this.props.Document, { title: this.props.Document.title + ".kvp", width: 300, height: 300 });
-        CollectionDockingView.Instance.AddRightSplit(kvp);
-    }
-    makeButton = (e: React.MouseEvent): void => {
-        let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+    deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }
+    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.Document, { width: 300, height: 300 }), "onRight") };
+    makeBtnClicked = (): void => {
+        let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
         if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally
             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 => {
-        const doc = Doc.MakeCopy(this.props.Document, false);
-        if (doc) {
-            CollectionDockingView.Instance.OpenFullScreen(doc);
-        }
-        ContextMenu.Instance.clearItems();
+    fullScreenClicked = (): void => {
+        CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
         SelectionManager.DeselectAll();
     }
 
@@ -332,7 +414,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
         cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
-        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "concierge-bell" });
+        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d3d765eed..7b642b299 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -102,7 +102,6 @@ export class FieldView extends React.Component<FieldViewProps> {
                     layoutKey={"layout"}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
-                    toggleMinimized={emptyFunction}
                     whenActiveChanged={this.props.whenActiveChanged}
                     bringToFront={emptyFunction} />
             );
-- 
cgit v1.2.3-70-g09d2


From 890a40ef8cc2efa7beea5722301b59ede5141238 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 30 May 2019 13:47:53 -0400
Subject: fixed context menus and tree view jitter.

---
 src/client/util/DragManager.ts                     |  4 +--
 src/client/util/TooltipTextMenu.tsx                |  2 +-
 src/client/views/ContextMenu.scss                  |  2 +-
 src/client/views/ContextMenuItem.tsx               |  2 +-
 src/client/views/MainView.tsx                      |  6 ++--
 .../views/collections/CollectionDockingView.tsx    |  6 +++-
 .../views/collections/CollectionTreeView.scss      |  6 +++-
 .../views/collections/CollectionTreeView.tsx       | 33 +++++++++++++---------
 src/client/views/collections/CollectionView.tsx    | 30 +++++++-------------
 src/client/views/nodes/DocumentView.tsx            | 31 ++++++++++++++------
 src/client/views/nodes/FormattedTextBox.tsx        | 25 +---------------
 src/client/views/nodes/ImageBox.tsx                | 11 ++++----
 .../authentication/models/current_user_utils.ts    | 20 ++++++-------
 13 files changed, 85 insertions(+), 93 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7f75a95f0..8f0cce095 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -26,7 +26,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         // if (this.props.isSelected() || this.props.isTopMost) {
         if (e.button === 0) {
             e.stopPropagation();
-            if (e.shiftKey) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
             } else {
                 document.addEventListener("pointermove", onRowMove);
@@ -264,7 +264,7 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
-            if (e.shiftKey) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
                     pageX: e.pageX,
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a1f80120f..5dd10f1bf 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -194,7 +194,7 @@ export class TooltipTextMenu {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                                 }
-                                else CollectionDockingView.Instance.AddRightSplit(f);
+                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f);
                             }
                         }));
                     }
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 61ae69179..74acba615 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -22,7 +22,7 @@
     color: $light-color;
 }
 
-.subMenu-cont {
+.contextMenu-subMenu-cont {
     position: absolute;
     display: flex;
     z-index: 1000;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 63347e076..fcda0db89 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -56,7 +56,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
         }
         else {
             let submenu = !this.overItem ? (null) :
-                <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+                <div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
             return (
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 74dafadc4..a093ffdec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -104,7 +104,9 @@ export class MainView extends React.Component {
         }, 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)) {
+
+            const targets = document.elementsFromPoint(e.x, e.y);
+            if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
                 ContextMenu.Instance.clearItems();
             }
         }), true);
@@ -163,7 +165,7 @@ export class MainView extends React.Component {
     }
 
     openNotifsCol = () => {
-        if (this._notifsCol) {
+        if (this._notifsCol && CollectionDockingView.Instance) {
             CollectionDockingView.Instance.AddRightSplit(this._notifsCol);
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b6076b5f7..dcc1bd95d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -22,6 +22,7 @@ import { ParentDocSelector } from './ParentDocumentSelector';
 import { DocumentManager } from '../../util/DocumentManager';
 import { CollectionViewType } from './CollectionBaseView';
 import { Id } from '../../../new_fields/FieldSymbols';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -415,7 +416,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
     get _stack(): any {
-        return (this.props as any).glContainer.parent.parent;
+        let parent = (this.props as any).glContainer.parent.parent;
+        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
+            return parent.parent.contentItems[1];
+        return parent;
     }
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2f0329fc4..bb3be0a73 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -48,7 +48,11 @@
         .treeViewItem-openRight {
             display: inline-block;
             // display: inline;
-            transform: translate(0px, -3px); //TODO Fix this
+            svg {
+                display:block;
+                padding:0px;
+                margin: 0px;
+            }
         }
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 85aab96d3..2814c0502 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,7 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import React = require("react");
 import { Document, listSpec } from '../../../new_fields/Schema';
-import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
+import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { ContextMenu } from '../ContextMenu';
@@ -19,6 +19,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Docs } from '../../documents/Documents';
 import { MainView } from '../MainView';
+import { CollectionViewType } from './CollectionBaseView';
 
 
 export interface TreeViewProps {
@@ -54,7 +55,7 @@ class TreeView extends React.Component<TreeViewProps> {
         if (this.props.document.dockingConfig) {
             MainView.Instance.openWorkspace(this.props.document);
         } else {
-            CollectionDockingView.Instance.AddRightSplit(this.props.document);
+            this.props.addDocTab(this.props.document, "openRight");
         }
     }
 
@@ -122,11 +123,11 @@ class TreeView extends React.Component<TreeViewProps> {
                     return true;
                 }}
             />);
-        let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []);
+        let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
         let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
             <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
                 <FontAwesomeIcon icon="angle-right" size="lg" />
-                <FontAwesomeIcon icon="angle-right" size="lg" />
+                {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */}
             </div>);
         return (
             <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
@@ -139,14 +140,19 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     onWorkspaceContextMenu = (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()) { // 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: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
-            ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight") });
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight") });
-            if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
-                ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+            if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" });
+                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" });
+                if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
+                    ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+                }
+                ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+            } else {
+                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             }
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             e.stopPropagation();
         }
@@ -207,11 +213,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         }
     }
     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
+        // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
+        if (!e.isPropagationStopped() && this.props.Document.excludeFromLibrary) { // excludeFromLibrary means this is the user document
             ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
-        }
-        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
-            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
+            ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });
         }
     }
     render() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a1e7c7c69..68eefab4c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,20 +1,19 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSquare, faTh, faTree, faSignature, faThList } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView';
 import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
-import { NumCast } from '../../../new_fields/Types';
-import { WidthSym, HeightSym } from '../../../new_fields/Doc';
+import { CollectionTreeView } from "./CollectionTreeView";
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -44,26 +43,17 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
 
-    freezeNativeDimensions = (e: React.MouseEvent): void => {
-        if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
-        }
-    }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!this.isAnnotationOverlay && !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.viewType = CollectionViewType.Freeform), icon: "signature" });
+            let subItems: ContextMenuProps[] = [];
+            subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
             if (CollectionBaseView.InSafeMode()) {
                 ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" });
             }
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
-            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
-            ContextMenu.Instance.addItem({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
-            ContextMenu.Instance.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
+            subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
+            subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
+            subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
+            ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a16a52ac6..69691b6f3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -29,6 +29,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
+import { ContextMenuProps } from '../ContextMenuItem';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
@@ -263,7 +264,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                             expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
                         }
                     }
-                    if (maxLocation && maxLocation !== "inPlace") {
+                    if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
                             expandedDocs.forEach(maxDoc =>
@@ -295,7 +296,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
-        if (e.shiftKey && e.buttons === 1) {
+        if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
             CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             e.stopPropagation();
         } else {
@@ -341,7 +342,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
     fullScreenClicked = (): void => {
-        CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
+        CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
         SelectionManager.DeselectAll();
     }
 
@@ -398,6 +399,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.templates = this.templates;
     }
 
+    freezeNativeDimensions = (e: React.MouseEvent): void => {
+        if (NumCast(this.props.Document.nativeWidth)) {
+            this.props.Document.proto!.nativeWidth = undefined;
+            this.props.Document.proto!.nativeHeight = undefined;
+
+        } else {
+            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+        }
+    }
+
     @action
     onContextMenu = (e: React.MouseEvent): void => {
         e.stopPropagation();
@@ -409,16 +421,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.preventDefault();
 
         const cm = ContextMenu.Instance;
-        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "desktop" });
-        cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
-        cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
-        cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+        let subitems: ContextMenuProps[] = [];
+        subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" });
+        subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
+        subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
+        cm.addItem({ description: "Open...", subitems: subitems });
+        cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
+                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight");
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index da584c811..5d93edaac 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -240,7 +240,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                             if (DocumentManager.Instance.getDocumentView(f)) {
                                 DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                             } else {
-                                CollectionDockingView.Instance.AddRightSplit(f);
+                                this.props.addDocTab && this.props.addDocTab(f, "onRight");
                             }
                         }
                     }));
@@ -274,29 +274,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
     }
-
-    freezeNativeDimensions = (e: React.MouseEvent): void => {
-        if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
-        }
-    }
-    specificContextMenu = (e: React.MouseEvent): void => {
-        if (!this._gotDown) {
-            e.preventDefault();
-            return;
-        }
-        ContextMenu.Instance.addItem({
-            description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
-            event: this.freezeNativeDimensions,
-            icon: "edit"
-        });
-    }
-
     onPointerWheel = (e: React.WheelEvent): void => {
         if (this.props.isSelected()) {
             e.stopPropagation();
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8ea6c5436..4c2b73b70 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -19,6 +19,7 @@ import { InkingControl } from '../InkingControl';
 import { Doc, WidthSym, HeightSym } from '../../../new_fields/Doc';
 import { faImage } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
+import { ContextMenuItemProps, ContextMenuProps } from '../ContextMenuItem';
 var path = require('path');
 
 
@@ -131,12 +132,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let field = Cast(this.Document[this.props.fieldKey], ImageField);
         if (field) {
             let url = field.url.href;
-            ContextMenu.Instance.addItem({
-                description: "Copy path", event: () => {
-                    Utils.CopyText(url);
-                }, icon: "expand-arrows-alt"
-            });
-            ContextMenu.Instance.addItem({
+            let subitems: ContextMenuProps[] = [];
+            subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" });
+            subitems.push({
                 description: "Rotate", event: action(() => {
                     this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360;
                     let nw = this.props.Document.nativeWidth;
@@ -147,6 +145,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                     this.props.Document.height = w;
                 }), icon: "expand-arrows-alt"
             });
+            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: subitems });
         }
     }
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index add347a88..e5b7a025b 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,21 +1,17 @@
-import { computed, observable, action, runInAction } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import * as rp from 'request-promise';
+import { DocServer } from "../../../client/DocServer";
 import { Docs } from "../../../client/documents/Documents";
-import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea";
+import { Gateway, NorthstarSettings } from "../../../client/northstar/manager/Gateway";
+import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
-import { RouteStore } from "../../RouteStore";
-import { DocServer } from "../../../client/DocServer";
-import { Doc, Opt, Field } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
 import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
-import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
 import { CollectionView } from "../../../client/views/collections/CollectionView";
-import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway";
-import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
-import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
-import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
-import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
+import { Cast } from "../../../new_fields/Types";
+import { RouteStore } from "../../RouteStore";
 
 export class CurrentUserUtils {
     private static curr_email: string;
-- 
cgit v1.2.3-70-g09d2


From e83c2a88303ae6d994a0ac2e84214947cac4d96d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 31 May 2019 00:44:30 -0400
Subject: Added Open Tab Alias and Open Right Alias to context menu Cleaned up
 MakeAlias

---
 src/client/util/TooltipTextMenu.tsx     |  1 +
 src/client/views/nodes/DocumentView.tsx |  2 ++
 src/new_fields/Doc.ts                   | 13 ++-----------
 3 files changed, 5 insertions(+), 11 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 5dd10f1bf..f517f757a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -198,6 +198,7 @@ export class TooltipTextMenu {
                             }
                         }));
                     }
+                    // TODO This should have an else to handle external links
                     e.stopPropagation();
                     e.preventDefault();
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7c142c3b..4905f0ddc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -424,7 +424,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let subitems: ContextMenuProps[] = [];
         subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" });
         subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
+        subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "inTab"), icon: "folder" });
         subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
+        subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" });
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems });
         cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0c74b8f65..7f7263cf1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -213,19 +213,10 @@ export namespace Doc {
 
 
     export function MakeAlias(doc: Doc) {
-        const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : undefined;
         const alias = new Doc;
-
-        if (!proto) {
-            alias.proto = doc;
-        } else {
-            PromiseValue(Cast(doc.proto, Doc)).then(proto => {
-                if (proto) {
-                    alias.proto = proto;
-                }
-            });
+        if (!GetT(doc, "isPrototype", "boolean", true)) {
+            alias.proto = doc.proto;
         }
-
         return alias;
     }
 
-- 
cgit v1.2.3-70-g09d2


From d1b0f462ced168f3a9ac5d007ba1219e98a3b6cf Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 31 May 2019 18:18:05 -0400
Subject: stacking views and more..

---
 src/client/views/MainOverlayTextBox.tsx            | 11 +++++++--
 .../views/collections/CollectionBaseView.tsx       |  2 +-
 .../views/collections/CollectionStackingView.scss  |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 28 +++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 13 ++++------
 6 files changed, 36 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 6e8e6f8ce..24327b995 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -7,6 +7,8 @@ import { Transform } from '../util/Transform';
 import "normalize.css";
 import "./MainOverlayTextBox.scss";
 import { FormattedTextBox } from './nodes/FormattedTextBox';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { Doc } from '../../new_fields/Doc';
 
 interface MainOverlayTextBoxProps {
 }
@@ -82,6 +84,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         document.removeEventListener('pointerup', this.textBoxUp);
     }
 
+    addDocTab = (doc: Doc, location: string) => {
+        if (true) { // location === "onRight") { need to figure out stack to add "inTab"
+            CollectionDockingView.Instance.AddRightSplit(doc);
+        }
+    }
     render() {
         if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
             let textRect = this._textTargetDiv.getBoundingClientRect();
@@ -91,10 +98,10 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                     style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
                     <FormattedTextBox fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
-                        ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={emptyFunction} />
+                        ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
                 </div>
             </ div>;
         }
-        else return (null);
+        else return (null); Z
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index e89b8be0f..734669893 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -120,7 +120,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
             if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
                 let zoom = NumCast(this.props.Document.scale, 1);
-                Doc.SetOnPrototype(doc, "zoomBasis", zoom);
+                // Doc.GetProto(doc).zoomBasis = zoom;
             }
         }
         return true;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 1bb0b2674..1ab335cc3 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -6,7 +6,7 @@
     display: flex;
     flex-direction: column;
     width: 100%;
-    height: 100%;
+    height: auto;
     position: absolute;
     overflow-y: auto;
     border-width: 0;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bf246d4ec..a616b5ac7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,24 +1,36 @@
 import React = require("react");
+import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { CollectionSubView, CollectionViewProps, SubCollectionViewProps } from "./CollectionSubView";
-import { Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
-import { DocumentView } from "../nodes/DocumentView";
-import { Transform } from "../../util/Transform";
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { NumCast } from "../../../new_fields/Types";
 import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionStackingView.scss";
-import { action, reaction, trace, computed } from "mobx";
-import { StrCast, NumCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { CollectionSubView } from "./CollectionSubView";
 
 
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _masonryGridRef: HTMLDivElement | null = null;
+    _heightDisposer?: IReactionDisposer;
     get gridGap() { return 10; }
     get gridSize() { return 20; }
     get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
+    componentDidMount() {
+        this._heightDisposer = reaction(() => [this.childDocs.map(d => d[HeightSym]()), this.props.PanelWidth()],
+            () => {
+                let hgt = (this.props.PanelWidth() > 500) ? this.props.Document[HeightSym]() :
+                    this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => height + d[HeightSym]() + this.gridGap, this.gridGap + 20 /* top margin */);
+                this.props.Document.height = hgt;
+            }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        if (this._heightDisposer) this._heightDisposer();
+    }
+
     @action
     moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
         this.props.removeDocument(doc);
@@ -37,7 +49,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @computed
     get children() {
-        return this.childDocs.map(d => {
+        return this.childDocs.filter(d => !d.isMinimized).map(d => {
             let colSpan = Math.ceil((this.itemWidth + this.gridGap) / (this.gridSize + this.gridGap));
             let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
             let dref = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 69691b6f3..f3a596558 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -328,7 +328,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }
-    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.Document, { width: 300, height: 300 }), "onRight") };
+    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") };
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5d93edaac..3b61c68dd 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -230,20 +230,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this._toolTipTextMenu.tooltip.style.opacity = "0";
             }
         }
+        let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey) {
             if (e.target && (e.target as any).href) {
                 let href = (e.target as any).href;
                 if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
                     let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
-                    DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
-                        if (f instanceof Doc) {
-                            if (DocumentManager.Instance.getDocumentView(f)) {
-                                DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
-                            } else {
-                                this.props.addDocTab && this.props.addDocTab(f, "onRight");
-                            }
-                        }
-                    }));
+                    DocServer.GetRefField(docid).then(f => {
+                        (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"))
+                    });
                 }
                 e.stopPropagation();
                 e.preventDefault();
-- 
cgit v1.2.3-70-g09d2


From 448996fbc234b82a27da952f85587a27cabe8881 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 1 Jun 2019 15:21:19 -0400
Subject: fixed stacking views to work with minimized documents

---
 .../views/collections/CollectionStackingView.tsx   | 30 +++++++++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 42 +++++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx            | 30 ++--------------
 3 files changed, 62 insertions(+), 40 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a616b5ac7..d06413154 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,11 +3,14 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, Cast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, returnOne, Utils } from "../../../Utils";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionStackingView.scss";
 import { CollectionSubView } from "./CollectionSubView";
+import { undoBatch } from "../../util/UndoManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import { List } from "../../../new_fields/List";
 
 
 
@@ -20,10 +23,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     get itemWidth() { return NumCast(this.props.Document.itemWidth, 250); }
 
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.childDocs.map(d => d[HeightSym]()), this.props.PanelWidth()],
+        this._heightDisposer = reaction(() => [this.childDocs.map(d => [d[HeightSym](), d.isMinimized]), this.props.PanelWidth()],
             () => {
                 let hgt = (this.props.PanelWidth() > 500) ? this.props.Document[HeightSym]() :
-                    this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => height + d[HeightSym]() + this.gridGap, this.gridGap + 20 /* top margin */);
+                    this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
+                        let rowSpan = Math.ceil((this.itemWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
+                        return height + rowSpan * (this.gridSize + this.gridGap);
+                    }, 10);
                 this.props.Document.height = hgt;
             }, { fireImmediately: true });
     }
@@ -47,6 +53,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._masonryGridRef = ele;
         this.createDropTarget(ele!);
     }
+    @undoBatch
+    @action
+    public collapseToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
+        SelectionManager.DeselectAll();
+        if (expandedDocs) {
+            let isMinimized: boolean | undefined;
+            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+                if (isMinimized === undefined) {
+                    isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                }
+                maximizedDoc.isMinimized = !isMinimized;
+            });
+        }
+    }
     @computed
     get children() {
         return this.childDocs.filter(d => !d.isMinimized).map(d => {
@@ -84,7 +104,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     parentActive={this.props.active}
                     addDocTab={this.props.addDocTab}
                     bringToFront={emptyFunction}
-                    whenActiveChanged={this.props.whenActiveChanged} />
+                    whenActiveChanged={this.props.whenActiveChanged}
+                    collapseToPoint={this.collapseToPoint}
+                />
             </div>);
         })
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 411e8c059..499b83c0f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,19 +1,17 @@
-import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
+import { computed, IReactionDisposer, reaction, action } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Utils } from "../../../Utils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SelectionManager } from "../../util/SelectionManager";
+import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { OmitKeys } from "../../../Utils";
 import { Transform } from "../../util/Transform";
-import { UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { DocComponent } from "../DocComponent";
 import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
+import { UndoManager } from "../../util/UndoManager";
+import { SelectionManager } from "../../util/SelectionManager";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
@@ -70,6 +68,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
+            collapseToPoint={this.collapseToPoint}
         />;
     }
 
@@ -88,6 +87,33 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (this._bringToFrontDisposer) this._bringToFrontDisposer();
     }
 
+    static _undoBatch?: UndoManager.Batch = undefined;
+    @action
+    public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
+        SelectionManager.DeselectAll();
+        if (expandedDocs) {
+            if (!CollectionFreeFormDocumentView._undoBatch) {
+                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            }
+            let isMinimized: boolean | undefined;
+            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
+                    if (isMinimized === undefined) {
+                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                    }
+                    maximizedDoc.willMaximize = isMinimized;
+                    maximizedDoc.isMinimized = false;
+                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
+                }
+            });
+            setTimeout(() => {
+                CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
+                CollectionFreeFormDocumentView._undoBatch = undefined;
+            }, 500);
+        }
+    }
+
     animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, maximizing: boolean) {
 
         setTimeout(() => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index be40efbfa..80c1292ae 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -75,7 +75,7 @@ export interface DocumentViewProps {
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc) => void;
     addDocTab: (doc: Doc, where: string) => void;
-    whenClicked?: () => void;
+    collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
 }
 
 const schema = createSchema({
@@ -195,32 +195,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    static _undoBatch?: UndoManager.Batch = undefined;
-    @action
-    public collapseToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
-        SelectionManager.DeselectAll();
-        if (expandedDocs) {
-            if (!DocumentView._undoBatch) {
-                DocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
-            }
-            let isMinimized: boolean | undefined;
-            expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
-                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
-                if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
-                    if (isMinimized === undefined) {
-                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
-                    }
-                    maximizedDoc.willMaximize = isMinimized;
-                    maximizedDoc.isMinimized = false;
-                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
-                }
-            });
-            setTimeout(() => {
-                DocumentView._undoBatch && DocumentView._undoBatch.end();
-                DocumentView._undoBatch = undefined;
-            }, 500);
-        }
-    }
     onClick = async (e: React.MouseEvent) => {
         e.stopPropagation();
         let altKey = e.altKey;
@@ -273,7 +247,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         }
                     } else {
                         let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
-                        this.collapseToPoint(scrpt, expandedProtoDocs);
+                        this.props.collapseToPoint && this.props.collapseToPoint(scrpt, expandedProtoDocs);
                     }
                 }
                 else if (linkedToDocs.length || linkedFromDocs.length) {
-- 
cgit v1.2.3-70-g09d2


From f4e6d81f4be7e26ec8866ea5a6e9d2433b89a79c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 2 Jun 2019 22:22:05 -0400
Subject: fixes to stacking view and freezing collections

---
 .../views/collections/CollectionStackingView.tsx    | 21 +++++++++++----------
 .../collections/collectionFreeForm/MarqueeView.tsx  | 12 ++++++++----
 src/client/views/nodes/DocumentView.tsx             | 21 +++++++++++++++------
 3 files changed, 34 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 25cc55be9..dcc7417ad 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -22,14 +22,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 2 * this.gridSize - this.gridGap : NumCast(this.props.Document.columnWidth, 250); }
 
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.childDocs.map(d => [d[HeightSym](), d.isMinimized]), this.props.PanelHeight(), this.props.PanelWidth()],
+        this._heightDisposer = reaction(() => [this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized]), this.columnWidth, this.props.PanelHeight()],
             () => {
                 if (this.singleColumn) {
                     this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
-                        let hgt = d[HeightSym]();
+                        let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
                         let nw = NumCast(d.nativeWidth);
                         let nh = NumCast(d.nativeHeight);
-                        if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, d[WidthSym]());
+                        if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, d[WidthSym]() / NumCast(d.zoomBasis, 1));
                         let rowSpan = Math.ceil((hgt + this.gridGap) / (this.gridSize + this.gridGap));
                         return height + rowSpan * (this.gridSize + this.gridGap);
                     }, 10);
@@ -76,19 +76,20 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.childDocs.filter(d => !d.isMinimized).map(d => {
             let dref = React.createRef<HTMLDivElement>();
             let script = undefined;
-            let colWidth = () => this.columnWidth;
+            let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym]() / NumCast(d.zoomBasis, 1), this.columnWidth) : this.columnWidth;
+            let margin = colWidth() < this.columnWidth ? "auto" : undefined;
             let rowHeight = () => {
-                let hgt = d[HeightSym]();
+                let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
                 let nw = NumCast(d.nativeWidth);
                 let nh = NumCast(d.nativeHeight);
-                if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, d[WidthSym]());
+                if (nw && nh) hgt = nh / nw * colWidth();
                 return hgt;
             }
             let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
             return <div className="colletionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
-                style={{ width: colWidth(), height: rowHeight() }} >
+                style={{ width: colWidth(), height: rowHeight(), marginLeft: margin, marginRight: margin }} >
                 <CollectionSchemaPreview
                     Document={d}
                     width={colWidth}
@@ -111,13 +112,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.childDocs.filter(d => !d.isMinimized).map(d => {
             let dref = React.createRef<HTMLDivElement>();
             let dxf = () => this.getDocTransform(d, dref.current!);
-            let colSpan = Math.ceil((this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap));
+            let colSpan = Math.ceil(Math.min(d[WidthSym](), this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap));
             let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap));
             let childFocus = (doc: Doc) => {
                 doc.libraryBrush = true;
                 this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered.
             }
-            return (<div className="colletionStackingView-masonryDoc"
+            return (<div className="collectionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
                 style={{
@@ -163,7 +164,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         width: `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
                         height: "100%",
                         overflow: "hidden",
-                        marginLeft: "auto", marginRight: "auto", position: "relative",
+                        marginRight: "auto", position: "relative",
                         gridGap: this.gridGap,
                         gridTemplateColumns: this.singleColumn ? undefined : `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`,
                         gridAutoRows: this.singleColumn ? undefined : `${this.gridSize}px`
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 14dbe1899..a19f40978 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,6 +20,8 @@ import { Template, Templates } from "../../Templates";
 import { SearchBox } from "../../SearchBox";
 import { DocServer } from "../../../DocServer";
 import { Id } from "../../../../new_fields/FieldSymbols";
+import { CollectionView } from "../CollectionView";
+import { CollectionViewType } from "../CollectionBaseView";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -284,10 +286,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 newCollection.proto!.summaryDoc = summary;
                 selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
-                summary.proto!.summarizedDocs = new List<Doc>(selected);
-                summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-
-                this.props.addLiveTextDocument(summary);
+                summary.proto!.subBulletDocs = new List<Doc>(selected);
+                //summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
+                summary.templates = new List<string>([Templates.Bullet.Layout]);
+                let container = Docs.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" });
+                container.viewType = CollectionViewType.Stacking;
+                this.props.addLiveTextDocument(container);
                 // });
             } else if (e.key === "S") {
                 await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 0.2 }).then((dataUrl) => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 80c1292ae..b9e000ec8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -375,12 +375,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     freezeNativeDimensions = (e: React.MouseEvent): void => {
         if (NumCast(this.props.Document.nativeWidth)) {
-            this.props.Document.proto!.nativeWidth = undefined;
-            this.props.Document.proto!.nativeHeight = undefined;
-
-        } else {
-            this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
-            this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+            let proto = Doc.GetProto(this.props.Document);
+            let nw = proto.nativeWidth;
+            let nh = proto.nativeHeight;
+            proto.nativeWidth = proto.nativeHeight = undefined;
+            this.props.Document.width = this.props.Document.frozenWidth;
+            this.props.Document.height = this.props.Document.frozenHeight;
+        }
+        else {
+            let scale = this.props.ScreenToLocalTransform().Scale * NumCast(this.props.Document.zoomBasis, 1);
+            let scr = this.screenRect();
+            let proto = Doc.GetProto(this.props.Document);
+            this.props.Document.frozenWidth = this.props.Document.width;
+            this.props.Document.frozenHeight = this.props.Document.height;
+            this.props.Document.height = proto.nativeHeight = scr.height * scale;
+            this.props.Document.width = proto.nativeWidth = scr.width * scale;
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 6f49d067b58caf6297f7ae7687cf05b627c27a1d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 4 Jun 2019 09:48:18 -0400
Subject: icon and stacking view fixes.

---
 src/client/util/DragManager.ts                          |  3 +--
 src/client/views/collections/CollectionStackingView.tsx | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx                 |  4 ++--
 src/client/views/nodes/IconBox.scss                     |  1 +
 4 files changed, 9 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 8f0cce095..1e84a0db0 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -279,8 +279,7 @@ export namespace DragManager {
             lastX = e.pageX;
             lastY = e.pageY;
             dragElements.map((dragElement, i) => (dragElement.style.transform =
-                `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px) 
-                scale(${scaleXs[i]}, ${scaleYs[i]})`)
+                `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
             );
         };
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index acd585268..da7ea50c6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -26,13 +26,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             () => {
                 if (this.singleColumn) {
                     this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
-                        let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
-                        let wid = d[WidthSym]() / NumCast(d.zoomBasis, 1);
+                        let hgt = d[HeightSym]();
+                        let wid = d[WidthSym]();
                         let nw = NumCast(d.nativeWidth);
                         let nh = NumCast(d.nativeHeight);
                         if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, wid);
                         return height + hgt + 2 * this.gridGap;
-                    }, this.gridGap * 2) * NumCast(this.props.Document.zoomBasis, 1);
+                    }, this.gridGap * 2);
                 }
             }, { fireImmediately: true });
     }
@@ -76,10 +76,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
             let dref = React.createRef<HTMLDivElement>();
             let script = undefined;
-            let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym]() / NumCast(d.zoomBasis, 1), this.columnWidth) : this.columnWidth;
+            let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
             let margin = colWidth() < this.columnWidth ? "auto" : undefined;
             let rowHeight = () => {
-                let hgt = d[HeightSym]() / NumCast(d.zoomBasis, 1);
+                let hgt = d[HeightSym]();
                 let nw = NumCast(d.nativeWidth);
                 let nh = NumCast(d.nativeHeight);
                 if (nw && nh) hgt = nh / nw * colWidth();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b9e000ec8..7750b9173 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -191,7 +191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (minimizedDoc) {
             let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(
                 NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
-            this.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
+            this.props.collapseToPoint && this.props.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
         }
     }
 
@@ -447,7 +447,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         var scaling = this.props.ContentScaling();
-        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto");
+        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
 
         return (
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
index 893dc2d36..488681027 100644
--- a/src/client/views/nodes/IconBox.scss
+++ b/src/client/views/nodes/IconBox.scss
@@ -10,6 +10,7 @@
     pointer-events: all;
     svg {
         width:  $MINIMIZED_ICON_SIZE !important;
+        height:  $MINIMIZED_ICON_SIZE !important;
         height: auto;
         background: white;
     }
-- 
cgit v1.2.3-70-g09d2

