From 9a4b4e9367d9d1fd245450db62124f0e530e4175 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Wed, 17 Jul 2019 11:23:19 -0400
Subject: fixed following links to regons seriously

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4532239d6..b91aaedee 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // this.props.Document.panX = panX;
         // this.props.Document.panY = panY;
         if (this.props.Document.scrollY) {
-            this.props.Document.scrollY = panY - this.props.Document[HeightSym]();
+            this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 33ba82d031266e87b6915de9f84bef2d914c2ae5 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 17 Jul 2019 16:21:21 -0400
Subject: patch for pdf's panning to wrong location.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b91aaedee..58218e641 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } from "../../.
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { emptyFunction, returnOne } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
@@ -263,7 +263,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
         const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
         this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
-        this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+        this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY;
         // this.props.Document.panX = panX;
         // this.props.Document.panY = panY;
         if (this.props.Document.scrollY) {
-- 
cgit v1.2.3-70-g09d2


From 2b98b5484b942361ab5e271fe9e09d09dc83fa60 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 19 Jul 2019 00:51:15 -0400
Subject: more cleanup for templates

---
 src/client/views/collections/CollectionView.tsx          |  6 +++---
 .../collectionFreeForm/CollectionFreeFormView.tsx        |  3 +--
 src/client/views/nodes/WebBox.tsx                        | 16 ----------------
 src/new_fields/Doc.ts                                    | 14 +++++++++-----
 4 files changed, 13 insertions(+), 26 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1b85a0cdb..b6b690e26 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, faSignature, faSquare, faTh, faImage, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Doc, DocListCast, WidthSym, HeightSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { undoBatch } from '../../util/UndoManager';
@@ -61,8 +61,8 @@ export class CollectionView extends React.Component<FieldViewProps> {
             ContextMenu.Instance.addItem({
                 description: "Apply Template", event: undoBatch(() => {
                     let otherdoc = new Doc();
-                    otherdoc.width = 100;
-                    otherdoc.height = 50;
+                    otherdoc.width = this.props.Document[WidthSym]();
+                    otherdoc.height = this.props.Document[HeightSym]();
                     Doc.GetProto(otherdoc).title = this.props.Document.title + "(..." + CollectionView._applyCount++ + ")"; // previously "applied"
                     Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
                     Doc.GetProto(otherdoc).miniLayout = StrCast(this.props.Document.miniLayout);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 58218e641..1997a6a4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { action, computed, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -516,7 +516,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     ]
     render() {
         const easing = () => this.props.Document.panTransformType === "Ease";
-
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
         return (
             <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f0a9ec6d8..162ac1d98 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -9,22 +9,6 @@ import React = require("react");
 import { InkTool } from "../../../new_fields/InkField";
 import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
 
-export function onYouTubeIframeAPIReady() {
-    console.log("player");
-    return;
-    let player = new YT.Player('player', {
-        events: {
-            'onReady': onPlayerReady
-        }
-    });
-}
-// must cast as any to set property on window
-const _global = (window /* browser */ || global /* node */) as any;
-_global.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
-
-function onPlayerReady(event: any) {
-    event.target.playVideo();
-}
 @observer
 export class WebBox extends React.Component<FieldViewProps> {
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 9a4b817d3..7bf1dec53 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -10,6 +10,7 @@ import { RefField, FieldId } from "./RefField";
 import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 import { List } from "./List";
+import { string } from "prop-types";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -344,20 +345,23 @@ export namespace Doc {
         // ... which means we change the layout to be an expanded view of the template layout.  
         // This allows the view override the template's properties and be referenceable as its own document.
 
-        let expandedTemplateLayout = templateLayoutDoc["_expanded_" + dataDoc[Id]];
+        let expandedTemplateLayout = dataDoc[templateLayoutDoc[Id]];
         if (expandedTemplateLayout instanceof Doc) {
             return expandedTemplateLayout;
         }
         if (expandedTemplateLayout === undefined && BoolCast(templateLayoutDoc.isTemplate)) {
             setTimeout(() => {
-                templateLayoutDoc["_expanded_" + dataDoc[Id]] = Doc.MakeDelegate(templateLayoutDoc);
-                (templateLayoutDoc["_expanded_" + dataDoc[Id]] as Doc).title = templateLayoutDoc.title + "[" + StrCast(dataDoc.title).match(/\.\.\.[0-9]*/) + "]";  // previously: "applied to"
-                (templateLayoutDoc["_expanded_" + dataDoc[Id]] as Doc).isExpandedTemplate = templateLayoutDoc;
+                let expandedDoc = Doc.MakeDelegate(templateLayoutDoc);
+                expandedDoc.title = templateLayoutDoc.title + "[" + StrCast(dataDoc.title).match(/\.\.\.[0-9]*/) + "]";
+                expandedDoc.isExpandedTemplate = templateLayoutDoc;
+                dataDoc[templateLayoutDoc[Id]] = expandedDoc;
             }, 0);
         }
-        return templateLayoutDoc;
+        return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
     }
 
+    let _pendingExpansions: Map<string, boolean> = new Map();
+
     export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
         const copy = new Doc;
         Object.keys(doc).forEach(key => {
-- 
cgit v1.2.3-70-g09d2


From fe188e93f2f8d61356bfdc3216273f72ae074e10 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tschicke@gmail.com>
Date: Mon, 22 Jul 2019 00:10:01 -0400
Subject: Added a bunch of REPL functionality

---
 src/client/util/Scripting.ts                       |  69 ++++++-
 src/client/views/OverlayView.scss                  |  25 ++-
 src/client/views/OverlayView.tsx                   |  52 ++++-
 src/client/views/ScriptingRepl.scss                |  39 ++++
 src/client/views/ScriptingRepl.tsx                 | 228 +++++++++++++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 7 files changed, 387 insertions(+), 30 deletions(-)

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

diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 62c2cfe85..46dc320b0 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,5 +1,7 @@
-// import * as ts from "typescript"
-let ts = (window as any).ts;
+import * as ts from "typescript";
+export { ts };
+// export const ts = (window as any).ts;
+
 // // @ts-ignore
 // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
 // // @ts-ignore
@@ -55,13 +57,35 @@ export namespace Scripting {
         }
         scriptingGlobals[n] = obj;
     }
+
+    export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
+        return { ..._scriptingGlobals, ...(globals || {}) };
+    }
+
+    export function setScriptingGlobals(globals: { [key: string]: any }) {
+        scriptingGlobals = globals;
+    }
+
+    export function resetScriptingGlobals() {
+        scriptingGlobals = _scriptingGlobals;
+    }
+
+    // const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]);
+    export function printNodeType(node: any, indentation = "") {
+        console.log(indentation + ts.SyntaxKind[node.kind]);
+    }
+
+    export function getGlobals() {
+        return Object.keys(scriptingGlobals);
+    }
 }
 
 export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
     Scripting.addGlobal(constructor);
 }
 
-const scriptingGlobals: { [name: string]: any } = {};
+const _scriptingGlobals: { [name: string]: any } = {};
+let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
 
 function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
     const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
@@ -162,6 +186,8 @@ class ScriptingCompilerHost {
     }
 }
 
+export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
+export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser };
 export interface ScriptOptions {
     requiredType?: string;
     addReturn?: boolean;
@@ -169,10 +195,23 @@ export interface ScriptOptions {
     capturedVariables?: { [name: string]: Field };
     typecheck?: boolean;
     editable?: boolean;
+    traverser?: TraverserParam;
+    transformer?: ts.TransformerFactory<ts.SourceFile>;
+    globals?: { [name: string]: any };
+}
+
+// function forEachNode(node:ts.Node, fn:(node:any) => void);
+function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = "") {
+    return onEnter(node, indentation) || ts.forEachChild(node, (n: any) => {
+        forEachNode(n, onEnter, onExit, indentation + "    ");
+    }) || (onExit && onExit(node, indentation));
 }
 
 export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
     const { requiredType = "", addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
+    if (options.globals) {
+        Scripting.setScriptingGlobals(options.globals);
+    }
     let host = new ScriptingCompilerHost;
     let paramNames: string[] = [];
     if ("this" in params || "this" in capturedVariables) {
@@ -192,10 +231,27 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
         paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
     }
     let paramString = paramList.join(", ");
+    if (options.traverser) {
+        const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+        const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
+        const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
+        forEachNode(sourceFile, onEnter, onLeave);
+    }
+    if (options.transformer) {
+        const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+        const result = ts.transform(sourceFile, [options.transformer]);
+        const transformed = result.transformed;
+        const printer = ts.createPrinter({
+            newLine: ts.NewLineKind.LineFeed
+        });
+        script = printer.printFile(transformed[0]);
+        result.dispose();
+    }
     let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
         ${addReturn ? `return ${script};` : script}
     })`;
     host.writeFile("file.ts", funcScript);
+
     if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
     let program = ts.createProgram(["file.ts"], {}, host);
     let testResult = program.emit();
@@ -203,7 +259,12 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
 
     let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
 
-    return Run(outputText, paramNames, diagnostics, script, options);
+    const result = Run(outputText, paramNames, diagnostics, script, options);
+
+    if (options.globals) {
+        Scripting.resetScriptingGlobals();
+    }
+    return result;
 }
 
 Scripting.addGlobal(CompileScript);
\ No newline at end of file
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 9d0abc96d..4d1e8cf0b 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -1,21 +1,42 @@
 .overlayWindow-outerDiv {
-    position: absolute;
     border-radius: 5px;
     overflow: hidden;
     display: flex;
     flex-direction: column;
 }
 
+.overlayWindow-outerDiv,
+.overlayView-wrapperDiv {
+    position: absolute;
+    z-index: 1;
+}
+
 .overlayWindow-titleBar {
-    height: 30px;
+    flex: 0 1 30px;
     background: darkslategray;
     color: whitesmoke;
     text-align: center;
     cursor: move;
 }
 
+.overlayWindow-content {
+    flex: 1 1 auto;
+    display: flex;
+    flex-direction: column;
+}
+
 .overlayWindow-closeButton {
     float: right;
     height: 30px;
     width: 30px;
+}
+
+.overlayWindow-resizeDragger {
+    background-color: red;
+    position: absolute;
+    right: 0px;
+    bottom: 0px;
+    width: 10px;
+    height: 10px;
+    cursor: nwse-resize;
 }
\ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 6b72abebf..2f2579057 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -25,16 +25,16 @@ export interface OverlayWindowProps {
 export class OverlayWindow extends React.Component<OverlayWindowProps> {
     @observable x: number;
     @observable y: number;
-    @observable width?: number;
-    @observable height?: number;
+    @observable width: number;
+    @observable height: number;
     constructor(props: OverlayWindowProps) {
         super(props);
 
         const opts = props.overlayOptions;
         this.x = opts.x;
         this.y = opts.y;
-        this.width = opts.width;
-        this.height = opts.height;
+        this.width = opts.width || 200;
+        this.height = opts.height || 200;
     }
 
     onPointerDown = (_: React.PointerEvent) => {
@@ -44,10 +44,27 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
         document.addEventListener("pointerup", this.onPointerUp);
     }
 
+    onResizerPointerDown = (_: React.PointerEvent) => {
+        document.removeEventListener("pointermove", this.onResizerPointerMove);
+        document.removeEventListener("pointerup", this.onResizerPointerUp);
+        document.addEventListener("pointermove", this.onResizerPointerMove);
+        document.addEventListener("pointerup", this.onResizerPointerUp);
+    }
+
     @action
     onPointerMove = (e: PointerEvent) => {
         this.x += e.movementX;
+        this.x = Math.max(Math.min(this.x, window.innerWidth - this.width), 0);
         this.y += e.movementY;
+        this.y = Math.max(Math.min(this.y, window.innerHeight - this.height), 0);
+    }
+
+    @action
+    onResizerPointerMove = (e: PointerEvent) => {
+        this.width += e.movementX;
+        this.width = Math.max(this.width, 30);
+        this.height += e.movementY;
+        this.height = Math.max(this.height, 30);
     }
 
     onPointerUp = (e: PointerEvent) => {
@@ -55,6 +72,11 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
         document.removeEventListener("pointerup", this.onPointerUp);
     }
 
+    onResizerPointerUp = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.onResizerPointerMove);
+        document.removeEventListener("pointerup", this.onResizerPointerUp);
+    }
+
     render() {
         return (
             <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
@@ -62,7 +84,10 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
                     {this.props.overlayOptions.title || "Untitled"}
                     <button onClick={this.props.onClick} className="overlayWindow-closeButton">X</button>
                 </div>
-                {this.props.children}
+                <div className="overlayWindow-content">
+                    {this.props.children}
+                </div>
+                <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown}></div>
             </div>
         );
     }
@@ -87,11 +112,26 @@ export class OverlayView extends React.Component {
             const index = this._elements.indexOf(ele);
             if (index !== -1) this._elements.splice(index, 1);
         });
-        ele = <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>{ele}</OverlayWindow>;
+        ele = <div key={Utils.GenerateGuid()} className="overlayView-wrapperDiv" style={{
+            transform: `translate(${options.x}px, ${options.y}px)`,
+            width: options.width,
+            height: options.height
+        }}>{ele}</div>;
         this._elements.push(ele);
         return remove;
     }
 
+    @action
+    addWindow(contents: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
+        const remove = action(() => {
+            const index = this._elements.indexOf(contents);
+            if (index !== -1) this._elements.splice(index, 1);
+        });
+        contents = <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>{contents}</OverlayWindow>;
+        this._elements.push(contents);
+        return remove;
+    }
+
     render() {
         return (
             <div>
diff --git a/src/client/views/ScriptingRepl.scss b/src/client/views/ScriptingRepl.scss
index 1eedb52fa..f1ef64193 100644
--- a/src/client/views/ScriptingRepl.scss
+++ b/src/client/views/ScriptingRepl.scss
@@ -1,5 +1,8 @@
 .scriptingRepl-outerContainer {
     background-color: whitesmoke;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
 }
 
 .scriptingRepl-resultContainer {
@@ -8,4 +11,40 @@
 
 .scriptingRepl-commandInput {
     width: 100%;
+}
+
+.scriptingRepl-commandResult,
+.scriptingRepl-commandString {
+    overflow-wrap: break-word;
+}
+
+.scriptingRepl-commandsContainer {
+    flex: 1 1 auto;
+    overflow-y: scroll;
+}
+
+.documentIcon-outerDiv {
+    background-color: white;
+    border-width: 1px;
+    border-style: solid;
+    border-radius: 25%;
+    padding: 2px;
+}
+
+.scriptingObject-icon {
+    padding: 3px;
+    cursor: pointer;
+}
+
+.scriptingObject-iconCollapsed {
+    padding-left: 4px;
+    padding-right: 5px;
+}
+
+.scriptingObject-fields {
+    padding-left: 10px;
+}
+
+.scriptingObject-leaf {
+    margin-left: 15px;
 }
\ No newline at end of file
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index bd6fc9dfb..6eabc7b70 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -2,32 +2,193 @@ import * as React from 'react';
 import { observer } from 'mobx-react';
 import { observable, action } from 'mobx';
 import './ScriptingRepl.scss';
-import { Scripting, CompileScript } from '../util/Scripting';
+import { Scripting, CompileScript, ts } from '../util/Scripting';
+import { DocumentManager } from '../util/DocumentManager';
+import { DocumentView } from './nodes/DocumentView';
+import { OverlayView } from './OverlayView';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
+
+library.add(faCaretDown);
+library.add(faCaretRight);
+
+@observer
+export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
+    render() {
+        this.props.view.props.ScreenToLocalTransform();
+        this.props.view.props.Document.width;
+        this.props.view.props.Document.height;
+        const screenCoords = this.props.view.screenRect();
+
+        return (
+            <div className="documentIcon-outerDiv" style={{
+                position: "absolute",
+                transform: `translate(${screenCoords.left + screenCoords.width / 2}px, ${screenCoords.top}px)`,
+            }}>
+                <p >${this.props.index}</p>
+            </div>
+        );
+    }
+}
+
+@observer
+export class DocumentIconContainer extends React.Component {
+    render() {
+        return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+    }
+}
+
+@observer
+export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void, value: { [key: string]: any }, name?: string }> {
+    @observable collapsed = true;
+
+    @action
+    toggle = () => {
+        this.collapsed = !this.collapsed;
+        this.props.scrollToBottom();
+    }
+
+    render() {
+        const val = this.props.value;
+        const proto = Object.getPrototypeOf(val);
+        const name = (proto && proto.constructor && proto.constructor.name) || String(val);
+        const title = this.props.name ? <><b>{this.props.name} : </b>{name}</> : name;
+        if (this.collapsed) {
+            return (
+                <div className="scriptingObject-collapsed">
+                    <span onClick={this.toggle} className="scriptingObject-icon scriptingObject-iconCollapsed"><FontAwesomeIcon icon="caret-right" size="sm" /></span>{title} (+{Object.keys(val).length})
+                </div>
+            );
+        } else {
+            return (
+                <div className="scriptingObject-open">
+                    <div>
+                        <span onClick={this.toggle} className="scriptingObject-icon"><FontAwesomeIcon icon="caret-down" size="sm" /></span>{title}
+                    </div>
+                    <div className="scriptingObject-fields">
+                        {Object.keys(val).map(key => <ScriptingValueDisplay {...this.props} name={key} />)}
+                    </div>
+                </div>
+            );
+        }
+    }
+}
+
+@observer
+export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: () => void, value: any, name?: string }> {
+    render() {
+        const val = this.props.name ? this.props.value[this.props.name] : this.props.value;
+        if (typeof val === "object") {
+            return <ScriptingObjectDisplay scrollToBottom={this.props.scrollToBottom} value={val} name={this.props.name} />;
+        } else if (typeof val === "function") {
+            const name = "[Function]";
+            const title = this.props.name ? <><b>{this.props.name} : </b>{name}</> : name;
+            return <div className="scriptingObject-leaf">{title}</div>;
+        } else {
+            const name = String(val);
+            const title = this.props.name ? <><b>{this.props.name} : </b>{name}</> : name;
+            return <div className="scriptingObject-leaf">{title}</div>;
+        }
+    }
+}
 
 @observer
 export class ScriptingRepl extends React.Component {
     @observable private commands: { command: string, result: any }[] = [];
 
     @observable private commandString: string = "";
+    private commandBuffer: string = "";
+
+    @observable private historyIndex: number = -1;
+
+    private commandsRef = React.createRef<HTMLDivElement>();
 
     private args: any = {};
 
+    getTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
+        const knownVars: { [name: string]: number } = {};
+        const usedDocuments: number[] = [];
+        Scripting.getGlobals().forEach(global => knownVars[global] = 1);
+        return root => {
+            function visit(node: ts.Node) {
+                node = ts.visitEachChild(node, visit, context);
+
+                if (ts.isIdentifier(node)) {
+                    const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+                    const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+                    if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+                        const match = node.text.match(/\$([0-9]+)/);
+                        if (match) {
+                            const m = parseInt(match[1]);
+                            usedDocuments.push(m);
+                        } else {
+                            return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+                        }
+                    }
+                }
+
+                return node;
+            }
+            return ts.visitNode(root, visit);
+        };
+    }
+
     @action
     onKeyDown = (e: React.KeyboardEvent) => {
-        if (e.key === "Enter") {
-            e.stopPropagation();
+        let stopProp = true;
+        switch (e.key) {
+            case "Enter": {
+                const docGlobals: { [name: string]: any } = {};
+                DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`$${i}`] = dv.props.Document);
+                const globals = Scripting.makeMutableGlobalsCopy(docGlobals);
+                const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer, globals });
+                if (!script.compiled) {
+                    return;
+                }
+                const result = script.run({ args: this.args });
+                if (!result.success) {
+                    return;
+                }
+                this.commands.push({ command: this.commandString, result: result.result });
+
+                this.maybeScrollToBottom();
 
-            const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" } });
-            if (!script.compiled) {
-                return;
+                this.commandString = "";
+                this.commandBuffer = "";
+                this.historyIndex = -1;
+                break;
             }
-            const result = script.run({ args: this.args });
-            if (!result.success) {
-                return;
+            case "ArrowUp": {
+                if (this.historyIndex < this.commands.length - 1) {
+                    this.historyIndex++;
+                    if (this.historyIndex === 0) {
+                        this.commandBuffer = this.commandString;
+                    }
+                    this.commandString = this.commands[this.commands.length - 1 - this.historyIndex].command;
+                }
+                break;
             }
-            this.commands.push({ command: this.commandString, result: result.result });
+            case "ArrowDown": {
+                if (this.historyIndex >= 0) {
+                    this.historyIndex--;
+                    if (this.historyIndex === -1) {
+                        this.commandString = this.commandBuffer;
+                        this.commandBuffer = "";
+                    } else {
+                        this.commandString = this.commands[this.commands.length - 1 - this.historyIndex].command;
+                    }
+                }
+                break;
+            }
+            default:
+                stopProp = false;
+                break;
+        }
 
-            this.commandString = "";
+        if (stopProp) {
+            e.stopPropagation();
+            e.preventDefault();
         }
     }
 
@@ -36,21 +197,56 @@ export class ScriptingRepl extends React.Component {
         this.commandString = e.target.value;
     }
 
+    private shouldScroll: boolean = false;
+    private maybeScrollToBottom = () => {
+        const ele = this.commandsRef.current;
+        if (ele && ele.scrollTop === (ele.scrollHeight - ele.offsetHeight)) {
+            this.shouldScroll = true;
+            this.forceUpdate();
+        }
+    }
+
+    private scrollToBottom() {
+        const ele = this.commandsRef.current;
+        ele && ele.scroll({ behavior: "auto", top: ele.scrollHeight });
+    }
+
+    componentDidUpdate() {
+        if (this.shouldScroll) {
+            this.shouldScroll = false;
+            this.scrollToBottom();
+        }
+    }
+
+    overlayDisposer?: () => void;
+    onFocus = () => {
+        if (this.overlayDisposer) {
+            this.overlayDisposer();
+        }
+        this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+    }
+
+    onBlur = () => {
+        this.overlayDisposer && this.overlayDisposer();
+    }
+
     render() {
         return (
             <div className="scriptingRepl-outerContainer">
-                <div className="scriptingRepl-commandsContainer">
-                    {this.commands.map(({ command, result }) => {
+                <div className="scriptingRepl-commandsContainer" ref={this.commandsRef}>
+                    {this.commands.map(({ command, result }, i) => {
                         return (
-                            <div className="scriptingRepl-resultContainer">
-                                <div className="scriptingRepl-commandString">{command}</div>
-                                <div className="scriptingRepl-commandResult">{String(result)}</div>
+                            <div className="scriptingRepl-resultContainer" key={i}>
+                                <div className="scriptingRepl-commandString">{command || <br />}</div>
+                                <div className="scriptingRepl-commandResult">{<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}</div>
                             </div>
                         );
                     })}
                 </div>
                 <input
                     className="scriptingRepl-commandInput"
+                    onFocus={this.onFocus}
+                    onBlur={this.onBlur}
                     value={this.commandString}
                     onChange={this.onChange}
                     onKeyDown={this.onKeyDown}></input>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 58218e641..652aca8f3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -502,7 +502,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         overlayDisposer();
                         setTimeout(() => docs.map(d => d.transition = undefined), 1200);
                     }} />;
-                    overlayDisposer = OverlayView.Instance.addElement(scriptingBox, options);
+                    overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
                 };
                 addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, { collection: "Doc", docs: "Doc[]" }, undefined);
                 addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 52ba643e0..7ff095573 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -557,7 +557,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
             }, icon: "search"
         });
-        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addElement(<ScriptingRepl />, { x: 100, y: 100 }) });
+        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200 }) });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
-- 
cgit v1.2.3-70-g09d2


From 33744cb0068d7dc470d0b39ca70344f256a9cad9 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tschicke@gmail.com>
Date: Mon, 22 Jul 2019 00:12:31 -0400
Subject: Added titles to overlay windows

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 652aca8f3..703873681 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -504,8 +504,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     }} />;
                     overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
                 };
-                addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, { collection: "Doc", docs: "Doc[]" }, undefined);
-                addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+                addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+                addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
             }
         });
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7ff095573..1c03bfc81 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -557,7 +557,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
             }, icon: "search"
         });
-        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200 }) });
+        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
-- 
cgit v1.2.3-70-g09d2


From b4ef60552f226e4022fac5092ea66f1b685bb08c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 22 Jul 2019 18:50:26 -0400
Subject: Started adding schema functionality

---
 src/client/util/Scripting.ts                       | 50 +++++++-----
 src/client/views/OverlayView.scss                  |  2 +-
 src/client/views/ScriptingRepl.tsx                 | 59 ++++++++------
 .../views/collections/CollectionSchemaView.tsx     | 92 ++++++++++++++++++++--
 src/client/views/collections/CollectionView.tsx    |  9 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 63 +++++++--------
 src/client/views/nodes/DocumentView.tsx            | 23 +++---
 7 files changed, 201 insertions(+), 97 deletions(-)

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

diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 46dc320b0..1d0916ac0 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -52,10 +52,10 @@ export namespace Scripting {
         } else {
             throw new Error("Must either register an object with a name, or give a name and an object");
         }
-        if (scriptingGlobals.hasOwnProperty(n)) {
+        if (_scriptingGlobals.hasOwnProperty(n)) {
             throw new Error(`Global with name ${n} is already registered, choose another name`);
         }
-        scriptingGlobals[n] = obj;
+        _scriptingGlobals[n] = obj;
     }
 
     export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
@@ -188,6 +188,10 @@ class ScriptingCompilerHost {
 
 export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
 export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser };
+export type Transformer = {
+    transformer: ts.TransformerFactory<ts.SourceFile>,
+    getVars?: () => { capturedVariables: { [name: string]: Field } }
+};
 export interface ScriptOptions {
     requiredType?: string;
     addReturn?: boolean;
@@ -196,7 +200,7 @@ export interface ScriptOptions {
     typecheck?: boolean;
     editable?: boolean;
     traverser?: TraverserParam;
-    transformer?: ts.TransformerFactory<ts.SourceFile>;
+    transformer?: Transformer;
     globals?: { [name: string]: any };
 }
 
@@ -213,6 +217,27 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
         Scripting.setScriptingGlobals(options.globals);
     }
     let host = new ScriptingCompilerHost;
+    if (options.traverser) {
+        const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+        const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
+        const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
+        forEachNode(sourceFile, onEnter, onLeave);
+    }
+    if (options.transformer) {
+        const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+        const result = ts.transform(sourceFile, [options.transformer.transformer]);
+        if (options.transformer.getVars) {
+            const newCaptures = options.transformer.getVars();
+            // tslint:disable-next-line: prefer-object-spread
+            options.capturedVariables = Object.assign(capturedVariables, newCaptures.capturedVariables) as any;
+        }
+        const transformed = result.transformed;
+        const printer = ts.createPrinter({
+            newLine: ts.NewLineKind.LineFeed
+        });
+        script = printer.printFile(transformed[0]);
+        result.dispose();
+    }
     let paramNames: string[] = [];
     if ("this" in params || "this" in capturedVariables) {
         paramNames.push("this");
@@ -227,26 +252,11 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     });
     for (const key in capturedVariables) {
         if (key === "this") continue;
+        const val = capturedVariables[key];
         paramNames.push(key);
-        paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
+        paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
     }
     let paramString = paramList.join(", ");
-    if (options.traverser) {
-        const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
-        const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
-        const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
-        forEachNode(sourceFile, onEnter, onLeave);
-    }
-    if (options.transformer) {
-        const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
-        const result = ts.transform(sourceFile, [options.transformer]);
-        const transformed = result.transformed;
-        const printer = ts.createPrinter({
-            newLine: ts.NewLineKind.LineFeed
-        });
-        script = printer.printFile(transformed[0]);
-        result.dispose();
-    }
     let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
         ${addReturn ? `return ${script};` : script}
     })`;
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 4d1e8cf0b..dc122497f 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -32,7 +32,7 @@
 }
 
 .overlayWindow-resizeDragger {
-    background-color: red;
+    background-color: rgb(0, 0, 0);
     position: absolute;
     right: 0px;
     bottom: 0px;
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 6eabc7b70..52273c357 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
 import { observer } from 'mobx-react';
 import { observable, action } from 'mobx';
 import './ScriptingRepl.scss';
-import { Scripting, CompileScript, ts } from '../util/Scripting';
+import { Scripting, CompileScript, ts, Transformer } from '../util/Scripting';
 import { DocumentManager } from '../util/DocumentManager';
 import { DocumentView } from './nodes/DocumentView';
 import { OverlayView } from './OverlayView';
@@ -17,8 +17,11 @@ library.add(faCaretRight);
 export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
     render() {
         this.props.view.props.ScreenToLocalTransform();
-        this.props.view.props.Document.width;
-        this.props.view.props.Document.height;
+        const doc = this.props.view.props.Document;
+        doc.width;
+        doc.height;
+        doc.x;
+        doc.y;
         const screenCoords = this.props.view.screenRect();
 
         return (
@@ -26,7 +29,7 @@ export class DocumentIcon extends React.Component<{ view: DocumentView, index: n
                 position: "absolute",
                 transform: `translate(${screenCoords.left + screenCoords.width / 2}px, ${screenCoords.top}px)`,
             }}>
-                <p >${this.props.index}</p>
+                <p>${this.props.index}</p>
             </div>
         );
     }
@@ -106,31 +109,35 @@ export class ScriptingRepl extends React.Component {
 
     private args: any = {};
 
-    getTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
-        const knownVars: { [name: string]: number } = {};
-        const usedDocuments: number[] = [];
-        Scripting.getGlobals().forEach(global => knownVars[global] = 1);
-        return root => {
-            function visit(node: ts.Node) {
-                node = ts.visitEachChild(node, visit, context);
+    getTransformer = (): Transformer => {
+        return {
+            transformer: context => {
+                const knownVars: { [name: string]: number } = {};
+                const usedDocuments: number[] = [];
+                Scripting.getGlobals().forEach(global => knownVars[global] = 1);
+                return root => {
+                    function visit(node: ts.Node) {
+                        node = ts.visitEachChild(node, visit, context);
 
-                if (ts.isIdentifier(node)) {
-                    const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
-                    const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
-                    if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
-                        const match = node.text.match(/\$([0-9]+)/);
-                        if (match) {
-                            const m = parseInt(match[1]);
-                            usedDocuments.push(m);
-                        } else {
-                            return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+                        if (ts.isIdentifier(node)) {
+                            const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+                            const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+                            if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+                                const match = node.text.match(/\$([0-9]+)/);
+                                if (match) {
+                                    const m = parseInt(match[1]);
+                                    usedDocuments.push(m);
+                                } else {
+                                    return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+                                }
+                            }
                         }
-                    }
-                }
 
-                return node;
+                        return node;
+                    }
+                    return ts.visitNode(root, visit);
+                };
             }
-            return ts.visitNode(root, visit);
         };
     }
 
@@ -142,7 +149,7 @@ export class ScriptingRepl extends React.Component {
                 const docGlobals: { [name: string]: any } = {};
                 DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`$${i}`] = dv.props.Document);
                 const globals = Scripting.makeMutableGlobalsCopy(docGlobals);
-                const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer, globals });
+                const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals });
                 if (!script.compiled) {
                     return;
                 }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index f72b1aa07..479fe2b0a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -15,7 +15,7 @@ import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_field
 import { Docs } from "../../documents/Documents";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { SetupDrag, DragManager } from "../../util/DragManager";
-import { CompileScript } from "../../util/Scripting";
+import { CompileScript, ts, Transformer } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
 import { ContextMenu } from "../ContextMenu";
@@ -31,6 +31,7 @@ import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import { undoBatch } from "../../util/UndoManager";
 import { timesSeries } from "async";
+import { ComputedField } from "../../../new_fields/ScriptField";
 
 
 library.add(faCog);
@@ -97,6 +98,78 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
         return this.props.Document;
     }
 
+    getField(row: number, col?: number) {
+        const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+        row = row % docs.length;
+        while (row < 0) row += docs.length;
+        const columns = this.columns;
+        const doc = docs[row];
+        if (col === undefined) {
+            return doc;
+        }
+        if (col >= 0 && col < columns.length) {
+            const column = this.columns[col];
+            return doc[column];
+        }
+        return undefined;
+    }
+
+    createTransformer = (row: number, col: number): Transformer => {
+        const self = this;
+        const captures: { [name: string]: Field } = {};
+
+        const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
+            return root => {
+                function visit(node: ts.Node) {
+                    node = ts.visitEachChild(node, visit, context);
+                    if (ts.isIdentifier(node)) {
+                        const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+                        const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+                        if (isntPropAccess && isntPropAssign) {
+                            if (node.text === "$r") {
+                                return ts.createNumericLiteral(row.toString());
+                            } else if (node.text === "$c") {
+                                return ts.createNumericLiteral(col.toString());
+                            } else if (node.text === "$") {
+                                if (ts.isCallExpression(node.parent)) {
+                                    captures.doc = self.props.Document;
+                                    captures.key = self.props.fieldKey;
+                                }
+                            }
+                        }
+                    }
+
+                    return node;
+                }
+                return ts.visitNode(root, visit);
+            };
+        };
+
+        const getVars = () => {
+            return { capturedVariables: captures };
+        };
+
+        return { transformer, getVars };
+    }
+
+    setComputed(script: string, doc: Doc, field: string, row: number, col: number): boolean {
+        script =
+            `const $ = (row:number, col?:number) => {
+                if(col === undefined) {
+                    return (doc as any)[key][row + ${row}];
+                }
+                return (doc as any)[key][row + ${row}][(doc as any).schemaColumns[col + ${col}]];
+            }
+            return ${script}`;
+        const compiled = CompileScript(script, { params: { this: Doc.name }, typecheck: true, transformer: this.createTransformer(row, col) });
+        if (compiled.compiled) {
+            doc[field] = new ComputedField(compiled);
+            return true;
+        }
+
+        return false;
+    }
+
     renderCell = (rowProps: CellInfo) => {
         let props: FieldViewProps = {
             Document: rowProps.original,
@@ -122,12 +195,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             (!this.props.CollectionView.props.isSelected() ? undefined :
                 SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         };
-        let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
-            const res = run({ this: doc });
+        let applyToDoc = (doc: Doc, row: number, column: number, run: (args?: { [name: string]: any }) => any) => {
+            const res = run({ this: doc, $r: row, $c: column, $: (r: number = 0, c: number = 0) => this.getField(r + row, c + column) });
             if (!res.success) return false;
             doc[props.fieldKey] = res.result;
             return true;
         };
+        const colIndex = this.columns.indexOf(rowProps.column.id!);
         return (
             <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>
                 <EditableView
@@ -142,21 +216,23 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                         return "";
                     }}
                     SetValue={(value: string) => {
-                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
+                        if (value.startsWith(":=")) {
+                            return this.setComputed(value.substring(2), props.Document, rowProps.column.id!, rowProps.index, colIndex);
+                        }
+                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
                         if (!script.compiled) {
                             return false;
                         }
-                        return applyToDoc(props.Document, script.run);
+                        return applyToDoc(props.Document, rowProps.index, colIndex, script.run);
                     }}
                     OnFillDown={async (value: string) => {
-                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
+                        let script = CompileScript(value, { addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
                         if (!script.compiled) {
                             return;
                         }
                         const run = script.run;
-                        //TODO This should be able to be refactored to compile the script once
                         const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
-                        val && val.forEach(doc => applyToDoc(doc, run));
+                        val && val.forEach((doc, i) => applyToDoc(doc, i, colIndex, run));
                     }}>
                 </EditableView>
             </div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 56750668d..ae99e930b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree, faFingerprint, faLaptopCode } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import { Doc } from '../../../new_fields/Doc';
@@ -24,6 +24,7 @@ library.add(faSquare);
 library.add(faProjectDiagram);
 library.add(faSignature);
 library.add(faThList);
+library.add(faFingerprint);
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
@@ -55,6 +56,12 @@ export class CollectionView extends React.Component<FieldViewProps> {
             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" });
+            switch (this.props.Document.viewType) {
+                case CollectionViewType.Freeform: {
+                    subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
+                    break;
+                }
+            }
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
             ContextMenu.Instance.addItem({
                 description: "Apply Template", event: undoBatch(() => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 703873681..dd577a467 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -477,43 +477,44 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }, "arrange contents");
             }
         });
-        ContextMenu.Instance.addItem({
-            description: "Add freeform arrangement",
-            event: () => {
-                let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
-                    let overlayDisposer: () => void = emptyFunction;
-                    const script = this.Document[key];
-                    let originalText: string | undefined = undefined;
-                    if (script) originalText = script.script.originalScript;
-                    // tslint:disable-next-line: no-unnecessary-callback-wrapper
-                    let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
-                        const script = CompileScript(text, {
-                            params,
-                            requiredType,
-                            typecheck: false
-                        });
-                        if (!script.compiled) {
-                            onError(script.errors.map(error => error.messageText).join("\n"));
-                            return;
-                        }
-                        const docs = DocListCast(this.Document[this.props.fieldKey]);
-                        docs.map(d => d.transition = "transform 1s");
-                        this.Document[key] = new ScriptField(script);
-                        overlayDisposer();
-                        setTimeout(() => docs.map(d => d.transition = undefined), 1200);
-                    }} />;
-                    overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
-                };
-                addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
-                addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
-            }
-        });
     }
 
     private childViews = () => [
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
     ]
+
+    public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
+        return () => {
+            let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+                let overlayDisposer: () => void = emptyFunction;
+                const script = Cast(doc[key], ScriptField);
+                let originalText: string | undefined = undefined;
+                if (script) originalText = script.script.originalScript;
+                // tslint:disable-next-line: no-unnecessary-callback-wrapper
+                let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+                    const script = CompileScript(text, {
+                        params,
+                        requiredType,
+                        typecheck: false
+                    });
+                    if (!script.compiled) {
+                        onError(script.errors.map(error => error.messageText).join("\n"));
+                        return;
+                    }
+                    const docs = DocListCast(doc[dataKey]);
+                    docs.map(d => d.transition = "transform 1s");
+                    doc[key] = new ScriptField(script);
+                    overlayDisposer();
+                    setTimeout(() => docs.map(d => d.transition = undefined), 1200);
+                }} />;
+                overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
+            };
+            addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+            addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+        };
+    }
+
     render() {
         const easing = () => this.props.Document.panTransformType === "Ease";
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1c03bfc81..7280dbcf6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -37,6 +37,7 @@ import { RouteStore } from '../../../server/RouteStore';
 import { FormattedTextBox } from './FormattedTextBox';
 import { OverlayView } from '../OverlayView';
 import { ScriptingRepl } from '../ScriptingRepl';
+import { ClientUtils } from '../../util/ClientUtils';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -58,6 +59,7 @@ library.add(fa.faCrosshairs);
 library.add(fa.faDesktop);
 library.add(fa.faUnlock);
 library.add(fa.faLock);
+library.add(fa.faLaptopCode);
 
 
 // const linkSchema = createSchema({
@@ -551,17 +553,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.removeDocument && this.props.removeDocument(this.props.Document);
             }, icon: "window-restore"
         });
-        cm.addItem({
-            description: "Find aliases", event: async () => {
-                const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
-            }, icon: "search"
-        });
-        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
-        cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
+        // cm.addItem({
+        //     description: "Find aliases", event: async () => {
+        //         const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+        //         this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
+        //     }, icon: "search"
+        // });
+        cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
-        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
-        cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+        if (!ClientUtils.RELEASE) {
+            cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.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" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
-- 
cgit v1.2.3-70-g09d2


From 86971952237b8bd01a23b52db662740126bd8477 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 23 Jul 2019 18:00:52 -0400
Subject: some fixes for fitToBox, templates, and showTitle

---
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/EditableView.tsx                  |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  3 +-
 .../views/collections/CollectionTreeView.tsx       | 76 +++++++++++++---------
 src/client/views/collections/CollectionView.tsx    | 15 +----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 +--
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 17 ++---
 src/new_fields/Doc.ts                              | 26 ++++++--
 9 files changed, 87 insertions(+), 67 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2f7bea365..989b35581 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -525,8 +525,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let actualdH = Math.max(height + (dH * scale), 20);
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
-                let proto = Doc.GetProto(element.props.Document);
-                let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect, false) && nwidth && nheight);
+                let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
+                let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect) && nwidth && nheight);
                 if (fixedAspect && (!nwidth || !nheight)) {
                     proto.nativeWidth = nwidth = doc.width || 0;
                     proto.nativeHeight = nheight = doc.height || 0;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index f2cdffd38..c66a92f48 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -93,7 +93,7 @@ export class EditableView extends React.Component<EditableProps> {
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
                     style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
                     onClick={this.onClick} >
-                    <span style={{ fontStyle: this.props.fontStyle }}>{this.props.contents}</span>
+                    <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span>
                 </div>
             );
         }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0e5f9a321..54b0e37b5 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils } from "../../../Utils";
+import { emptyFunction, Utils, returnTrue } from "../../../Utils";
 import { CollectionSchemaPreview } from "./CollectionSchemaView";
 import "./CollectionStackingView.scss";
 import { CollectionSubView } from "./CollectionSubView";
@@ -74,6 +74,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             DataDocument={resolvedDataDoc}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth}
+            fitToBox={true}
             width={width}
             height={height}
             getTransform={finalDxf}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d05cc375e..f98629c5b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -74,15 +74,15 @@ class TreeView extends React.Component<TreeViewProps> {
     @observable _collapsed: boolean = true;
 
     @computed get fieldKey() {
-        let keys = Array.from(Object.keys(this.resolvedDataDoc));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
-        if (this.resolvedDataDoc.proto instanceof Doc) {
-            let arr = Array.from(Object.keys(this.resolvedDataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+        let keys = Array.from(Object.keys(this.dataDoc));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+        if (this.dataDoc.proto instanceof Doc) {
+            let arr = Array.from(Object.keys(this.dataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
             keys.push(...arr);
             while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
         }
         let keyList: string[] = [];
         keys.map(key => {
-            let docList = Cast(this.resolvedDataDoc[key], listSpec(Doc));
+            let docList = Cast(this.dataDoc[key], listSpec(Doc));
             if (docList && docList.length > 0) {
                 keyList.push(key);
             }
@@ -94,7 +94,16 @@ class TreeView extends React.Component<TreeViewProps> {
         return keyList.length ? keyList[0] : "data";
     }
 
-    @computed get resolvedDataDoc() { return BoolCast(this.props.document.isTemplate) && this.props.dataDoc ? this.props.dataDoc : this.props.document; }
+    @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
+    @computed get resolvedDataDoc() {
+        if (this.props.dataDoc === undefined && this.props.document.layout instanceof Doc) {
+            // if there is no dataDoc (ie, we're not rendering a template layout), but this document
+            // has a template layout document, then we will render the template layout but use 
+            // this document as the data document for the layout.
+            return this.props.document;
+        }
+        return this.props.dataDoc ? this.props.dataDoc : undefined;
+    }
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer && this._treedropDisposer();
@@ -103,7 +112,7 @@ class TreeView extends React.Component<TreeViewProps> {
         }
     }
 
-    @undoBatch delete = () => this.props.deleteDoc(this.resolvedDataDoc);
+    @undoBatch delete = () => this.props.deleteDoc(this.dataDoc);
     @undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight");
 
     onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
@@ -135,7 +144,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @action
     remove = (document: Document, key: string): boolean => {
-        let children = Cast(this.resolvedDataDoc[key], listSpec(Doc), []);
+        let children = Cast(this.dataDoc[key], listSpec(Doc), []);
         if (children.indexOf(document) !== -1) {
             children.splice(children.indexOf(document), 1);
             return true;
@@ -151,8 +160,8 @@ class TreeView extends React.Component<TreeViewProps> {
     indent = () => this.props.addDocument(this.props.document) && this.delete()
 
     renderBullet() {
-        let docList = Cast(this.resolvedDataDoc[this.fieldKey], listSpec(Doc));
-        let doc = Cast(this.resolvedDataDoc[this.fieldKey], Doc);
+        let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc));
+        let doc = Cast(this.dataDoc[this.fieldKey], Doc);
         let isDoc = doc instanceof Doc || docList;
         let c;
         return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
@@ -164,16 +173,17 @@ class TreeView extends React.Component<TreeViewProps> {
     editableView = (key: string, style?: string) => (<EditableView
         oneLine={true}
         display={"inline"}
-        editing={this.resolvedDataDoc[Id] === TreeView.loadId}
+        editing={this.dataDoc[Id] === TreeView.loadId}
         contents={StrCast(this.props.document[key])}
         height={36}
         fontStyle={style}
         fontSize={12}
         GetValue={() => StrCast(this.props.document[key])}
-        SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc)[key] = value) ? true : true}
+        SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true}
         OnFillDown={(value: string) => {
-            Doc.GetProto(this.resolvedDataDoc)[key] = value;
-            let doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+            Doc.GetProto(this.dataDoc)[key] = value;
+            let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined;
+            if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
             TreeView.loadId = doc[Id];
             return this.props.addDocument(doc);
         }}
@@ -181,16 +191,16 @@ class TreeView extends React.Component<TreeViewProps> {
     />)
 
     @computed get keyList() {
-        let keys = Array.from(Object.keys(this.resolvedDataDoc));
-        if (this.resolvedDataDoc.proto instanceof Doc) {
-            keys.push(...Array.from(Object.keys(this.resolvedDataDoc.proto)));
+        let keys = Array.from(Object.keys(this.dataDoc));
+        if (this.dataDoc.proto instanceof Doc) {
+            keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
         }
         let keyList: string[] = keys.reduce((l, key) => {
-            let listspec = DocListCast(this.resolvedDataDoc[key]);
+            let listspec = DocListCast(this.dataDoc[key]);
             if (listspec && listspec.length) return [...l, key];
             return l;
         }, [] as string[]);
-        keys.map(key => Cast(this.resolvedDataDoc[key], Doc) instanceof Doc && keyList.push(key));
+        keys.map(key => Cast(this.dataDoc[key], Doc) instanceof Doc && (Cast(this.dataDoc[key], Doc) as Doc).type !== undefined && keyList.push(key));
         if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
         if (keyList.indexOf(this.fieldKey) !== -1) {
             keyList.splice(keyList.indexOf(this.fieldKey), 1);
@@ -203,7 +213,7 @@ class TreeView extends React.Component<TreeViewProps> {
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = SetupDrag(reference, () => this.resolvedDataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
+        let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
 
         let headerElements = (
             <span className="collectionTreeView-keyHeader" key={this._chosenKey + "chosen"}
@@ -215,7 +225,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 {this._chosenKey}
             </span>);
         let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
-        let openRight = dataDocs && dataDocs.indexOf(this.resolvedDataDoc) !== -1 ? (null) : (
+        let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : (
             <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
                 <FontAwesomeIcon icon="angle-right" size="lg" />
             </div>);
@@ -241,12 +251,12 @@ class TreeView extends React.Component<TreeViewProps> {
             if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
                 ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
                 ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
-                if (DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).length) {
-                    ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" });
+                if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
+                    ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.dataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" });
                 }
                 ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
             } else {
-                ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)), icon: "caret-square-right" });
+                ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.dataDoc)), icon: "caret-square-right" });
                 ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
             }
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
@@ -274,7 +284,7 @@ class TreeView extends React.Component<TreeViewProps> {
             if (de.data.draggedDocuments[0] === this.props.document) return true;
             let addDoc = (doc: Doc) => this.props.addDocument(doc, this.resolvedDataDoc, before);
             if (inside) {
-                let docList = Cast(this.resolvedDataDoc.data, listSpec(Doc));
+                let docList = Cast(this.dataDoc.data, listSpec(Doc));
                 if (docList !== undefined) {
                     addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
                 }
@@ -326,7 +336,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @computed get boundsOfCollectionDocument() {
         if (StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1) return undefined;
-        let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
+        let layoutDoc = this.props.document;
         return Doc.ComputeContentBounds(DocListCast(layoutDoc.data));
     }
     docWidth = () => {
@@ -344,27 +354,30 @@ class TreeView extends React.Component<TreeViewProps> {
         })());
     }
 
+    noOverlays = (doc: Doc) => { return { title: "", caption: "" } };
+
     render() {
         let contentElement: (JSX.Element | null) = null;
-        let docList = Cast(this.resolvedDataDoc[this._chosenKey], listSpec(Doc));
+        let docList = Cast(this.dataDoc[this._chosenKey], listSpec(Doc));
         let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
-        let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.resolvedDataDoc, this._chosenKey, doc, addBefore, before);
-        let doc = Cast(this.resolvedDataDoc[this._chosenKey], Doc);
+        let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this._chosenKey, doc, addBefore, before);
+        let doc = Cast(this.dataDoc[this._chosenKey], Doc);
 
         if (!this._collapsed) {
             if (!this.props.document.embed) {
                 contentElement = <ul key={this._chosenKey + "more"}>
                     {this._chosenKey === "links" ? this.renderLinks() :
-                        TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.props.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
+                        TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this._chosenKey, addDoc, remDoc, this.move,
                             this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
                 </ul >;
             } else {
-                let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
+                let layoutDoc = this.props.document;
                 contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
                     <CollectionSchemaPreview
                         Document={layoutDoc}
                         DataDocument={this.resolvedDataDoc}
                         renderDepth={this.props.renderDepth}
+                        showOverlays={this.noOverlays}
                         fitToBox={this.boundsOfCollectionDocument !== undefined}
                         width={this.docWidth}
                         height={this.docHeight}
@@ -547,7 +560,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
                     OnFillDown={(value: string) => {
                         Doc.GetProto(this.props.Document).title = value;
-                        let doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+                        let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined;
+                        if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         TreeView.loadId = doc[Id];
                         Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
                     }} />
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 045c8531e..4a51a1f58 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -50,7 +50,6 @@ export class CollectionView extends React.Component<FieldViewProps> {
 
     get isAnnotationOverlay() { return this.props.fieldExt ? true : false; }
 
-    static _applyCount: number = 0;
     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
             let subItems: ContextMenuProps[] = [];
@@ -63,19 +62,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
             subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "ellipsis-v" });
             subItems.push({ description: "Masonry", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Masonry), icon: "columns" });
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
-            ContextMenu.Instance.addItem({
-                description: "Apply Template", event: undoBatch(() => {
-                    let otherdoc = new Doc();
-                    otherdoc.width = this.props.Document[WidthSym]();
-                    otherdoc.height = this.props.Document[HeightSym]();
-                    otherdoc.title = this.props.Document.title + "(..." + CollectionView._applyCount++ + ")"; // previously "applied"
-                    otherdoc.layout = Doc.MakeDelegate(this.props.Document);
-                    otherdoc.miniLayout = StrCast(this.props.Document.miniLayout);
-                    otherdoc.detailedLayout = otherdoc.layout;
-                    otherdoc.type = DocumentType.TEMPLATE;
-                    this.props.addDocTab && this.props.addDocTab(otherdoc, undefined, "onRight");
-                }), icon: "project-diagram"
-            });
+            ContextMenu.Instance.addItem({ description: "Apply Template", event: undoBatch(() => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight")), icon: "project-diagram" });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 703873681..6bb082b66 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -195,10 +195,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     this._pheight / this.zoomScaling());
                 let panelwidth = panelDim[0];
                 let panelheight = panelDim[1];
-                if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
-                if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
-                if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
-                if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
+                // if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
+                // if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
+                // if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
+                // if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
             }
             this.setPan(x - dx, y - dy);
             this._lastX = e.pageX;
@@ -358,6 +358,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
         let self = this;
         let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
+        resolvedDataDoc && Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
         let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
         return {
             DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ed6b224a7..ef65c12cf 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -64,7 +64,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     get dataDoc() {
         if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
-            // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
+            // if there is no dataDoc (ie, we're not rendering a template layout), but this document
             // has a template layout document, then we will render the template layout but use 
             // this document as the data document for the layout.
             return this.props.Document;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 907ba3713..09a1b49fe 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -503,7 +503,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     freezeNativeDimensions = (): void => {
-        let proto = Doc.GetProto(this.props.Document);
+        let proto = this.props.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document);
         if (proto.ignoreAspect === undefined && !proto.nativeWidth) {
             proto.nativeWidth = this.props.PanelWidth();
             proto.nativeHeight = this.props.PanelHeight();
@@ -629,10 +629,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.props.Document) : undefined;
-        let showTitle = showOverlays && showOverlays.title ? showOverlays.title : StrCast(this.props.Document.showTitle);
-        let showCaption = showOverlays && showOverlays.caption ? showOverlays.caption : StrCast(this.props.Document.showCaption);
+        let showTitle = showOverlays && showOverlays.title !== "undefined" ? showOverlays.title : StrCast(this.props.Document.showTitle);
+        let showCaption = showOverlays && showOverlays.caption !== "undefined" ? showOverlays.caption : StrCast(this.props.Document.showCaption);
         let templates = Cast(this.props.Document.templates, listSpec("string"));
-        if (templates instanceof List) {
+        if (!showOverlays && templates instanceof List) {
             templates.map(str => {
                 if (str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
                 if (str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption";
@@ -663,12 +663,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 {!showTitle && !showCaption ? this.contents :
                     <div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
 
-                        <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 25px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "25px" : undefined }}>
+                        <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 33px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
                             {this.contents}
                         </div>
                         {!showTitle ? (null) :
                             <div style={{
-                                position: showTextTitle ? "relative" : "absolute", top: 0, textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
+                                position: showTextTitle ? "relative" : "absolute", top: 0, padding: "4px", textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
                                 pointerEvents: "all",
                                 overflow: "hidden", width: `${100 * this.props.ContentScaling()}%`, height: 25, background: "rgba(0, 0, 0, .4)", color: "white",
                                 transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})`
@@ -677,8 +677,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                                     contents={this.props.Document[showTitle]}
                                     display={"block"}
                                     height={72}
-                                    GetValue={() => StrCast(this.props.Document[showTitle])}
-                                    SetValue={(value: string) => (Doc.GetProto(this.props.Document)[showTitle] = value) ? true : true}
+                                    fontSize={12}
+                                    GetValue={() => StrCast(this.props.Document[showTitle!])}
+                                    SetValue={(value: string) => (Doc.GetProto(this.props.Document)[showTitle!] = value) ? true : true}
                                 />
                             </div>
                         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 2ad6ae5f0..5ae0753d8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -244,7 +244,7 @@ export namespace Doc {
         let r = (doc === other);
         let r2 = (doc.proto === other);
         let r3 = (other.proto === doc);
-        let r4 = (doc.proto === other.proto);
+        let r4 = (doc.proto === other.proto && other.proto !== undefined);
         return r || r2 || r3 || r4;
     }
 
@@ -298,7 +298,7 @@ export namespace Doc {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
             };
-        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
         return bounds;
     }
 
@@ -350,19 +350,21 @@ export namespace Doc {
         if (expandedTemplateLayout instanceof Doc) {
             return expandedTemplateLayout;
         }
+        expandedTemplateLayout = dataDoc[templateLayoutDoc.title + templateLayoutDoc[Id]];
+        if (expandedTemplateLayout instanceof Doc) {
+            return expandedTemplateLayout;
+        }
         if (expandedTemplateLayout === undefined && BoolCast(templateLayoutDoc.isTemplate)) {
             setTimeout(() => {
                 let expandedDoc = Doc.MakeDelegate(templateLayoutDoc);
                 expandedDoc.title = templateLayoutDoc.title + "[" + StrCast(dataDoc.title).match(/\.\.\.[0-9]*/) + "]";
                 expandedDoc.isExpandedTemplate = templateLayoutDoc;
-                dataDoc[templateLayoutDoc[Id]] = expandedDoc;
+                dataDoc[templateLayoutDoc.title + templateLayoutDoc[Id]] = expandedDoc;
             }, 0);
         }
         return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
     }
 
-    let _pendingExpansions: Map<string, boolean> = new Map();
-
     export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
         const copy = new Doc;
         Object.keys(doc).forEach(key => {
@@ -398,6 +400,20 @@ export namespace Doc {
         return undefined;
     }
 
+    let _applyCount: number = 0;
+    export function ApplyTemplate(templateDoc: Doc) {
+        if (!templateDoc) return undefined;
+        let otherdoc = new Doc();
+        otherdoc.width = templateDoc[WidthSym]();
+        otherdoc.height = templateDoc[HeightSym]();
+        otherdoc.title = templateDoc.title + "(..." + _applyCount++ + ")";
+        otherdoc.layout = Doc.MakeDelegate(templateDoc);
+        otherdoc.miniLayout = StrCast(templateDoc.miniLayout);
+        otherdoc.detailedLayout = otherdoc.layout;
+        otherdoc.type = DocumentType.TEMPLATE;
+        return otherdoc;
+    }
+
     export function MakeTemplate(fieldTemplate: Doc, metaKey: string, proto: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
-- 
cgit v1.2.3-70-g09d2


From 6d25848a9e8614c1debc8550e42fc706f77883db Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 23 Jul 2019 19:07:08 -0400
Subject: more template stuff

---
 src/client/views/collections/CollectionTreeView.tsx  | 20 +++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx    |  9 +++++++--
 src/new_fields/Doc.ts                                |  9 +++++++--
 3 files changed, 25 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f98629c5b..ca5812eba 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -74,9 +74,10 @@ class TreeView extends React.Component<TreeViewProps> {
     @observable _collapsed: boolean = true;
 
     @computed get fieldKey() {
-        let keys = Array.from(Object.keys(this.dataDoc));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
-        if (this.dataDoc.proto instanceof Doc) {
-            let arr = Array.from(Object.keys(this.dataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+        let target = this.props.document.layout && this.dataDoc ? this.dataDoc : this.props.document;
+        let keys = Array.from(Object.keys(target));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+        if (target.proto instanceof Doc) {
+            let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
             keys.push(...arr);
             while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
         }
@@ -191,16 +192,17 @@ class TreeView extends React.Component<TreeViewProps> {
     />)
 
     @computed get keyList() {
-        let keys = Array.from(Object.keys(this.dataDoc));
-        if (this.dataDoc.proto instanceof Doc) {
-            keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
+        let target = this.props.document.layout && this.dataDoc ? this.dataDoc : this.props.document;
+        let keys = Array.from(Object.keys(target));
+        if (target.proto instanceof Doc) {
+            keys.push(...Array.from(Object.keys(target.proto)));
         }
         let keyList: string[] = keys.reduce((l, key) => {
-            let listspec = DocListCast(this.dataDoc[key]);
+            let listspec = DocListCast(target[key]);
             if (listspec && listspec.length) return [...l, key];
             return l;
         }, [] as string[]);
-        keys.map(key => Cast(this.dataDoc[key], Doc) instanceof Doc && (Cast(this.dataDoc[key], Doc) as Doc).type !== undefined && keyList.push(key));
+        keys.map(key => Cast(target[key], Doc) instanceof Doc && keyList.push(key));
         if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
         if (keyList.indexOf(this.fieldKey) !== -1) {
             keyList.splice(keyList.indexOf(this.fieldKey), 1);
@@ -361,7 +363,7 @@ class TreeView extends React.Component<TreeViewProps> {
         let docList = Cast(this.dataDoc[this._chosenKey], listSpec(Doc));
         let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
         let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this._chosenKey, doc, addBefore, before);
-        let doc = Cast(this.dataDoc[this._chosenKey], Doc);
+        let doc = Cast((this.props.document.layout ? this.dataDoc : this.props.document)[this._chosenKey], Doc);
 
         if (!this._collapsed) {
             if (!this.props.document.embed) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6bb082b66..e7d2a66e5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,6 +31,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
+import { resolve } from "bluebird";
 
 
 export const panZoomSchema = createSchema({
@@ -358,8 +359,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
         let self = this;
         let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
-        resolvedDataDoc && Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
-        let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+        let layoutDoc = childDocLayout;
+        if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
+            Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
+            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+            layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
+        } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
         return {
             DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
             Document: layoutDoc,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5ae0753d8..e4bf4a6c1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -337,11 +337,15 @@ export namespace Doc {
         return Doc.MakeDelegate(doc); // bcz?
     }
 
-    export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
+    export function WillExpandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
         let resolvedDataDoc = (templateLayoutDoc !== dataDoc) ? dataDoc : undefined;
         if (!dataDoc || !(templateLayoutDoc && !(Cast(templateLayoutDoc.layout, Doc) instanceof Doc) && resolvedDataDoc && resolvedDataDoc !== templateLayoutDoc)) {
-            return templateLayoutDoc;
+            return false;
         }
+        return true;
+    }
+    export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
+        if (!WillExpandTemplateLayout(templateLayoutDoc, dataDoc) || !dataDoc) return templateLayoutDoc;
         // if we have a data doc that doesn't match the layout, then we're rendering a template.
         // ... which means we change the layout to be an expanded view of the template layout.  
         // This allows the view override the template's properties and be referenceable as its own document.
@@ -433,6 +437,7 @@ export namespace Doc {
         layoutDelegate.layout = layout;
 
         fieldTemplate.title = metaKey;
+        fieldTemplate.templateField = metaKey;
         fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
         fieldTemplate.backgroundLayout = backgroundLayout;
         fieldTemplate.nativeWidth = nw;
-- 
cgit v1.2.3-70-g09d2


From cb95d4b43238538f59babcaa0b0d9dabb9b393b7 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 23 Jul 2019 22:07:13 -0400
Subject: Ink recognition and presentation view toggle

---
 src/client/cognitive_services/CognitiveServices.ts | 86 +++++++++++++++++++++-
 src/client/views/InkingCanvas.tsx                  |  4 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 +++
 .../views/presentationview/PresentationView.tsx    | 12 ++-
 src/new_fields/FieldSymbols.ts                     |  2 +-
 src/new_fields/InkField.ts                         |  6 +-
 src/server/index.ts                                | 19 ++---
 8 files changed, 124 insertions(+), 18 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d4085cf76..71e4276dc 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -8,10 +8,12 @@ import { RouteStore } from "../../server/RouteStore";
 import { Utils } from "../../Utils";
 import { CompileScript } from "../util/Scripting";
 import { ComputedField } from "../../new_fields/ScriptField";
+import { InkData } from "../../new_fields/InkField";
 
 export enum Services {
     ComputerVision = "vision",
-    Face = "face"
+    Face = "face",
+    Handwriting = "handwriting"
 }
 
 export enum Confidence {
@@ -132,4 +134,86 @@ export namespace CognitiveServices {
 
     }
 
+    export namespace Inking {
+
+        export interface AzureStrokeData {
+            id: number;
+            points: string;
+            language?: string;
+        }
+
+        export interface HandwritingUnit {
+            version: number;
+            language: string;
+            unit: string;
+            strokes: AzureStrokeData[];
+        }
+
+        export const analyze = async (inkData: InkData, target: Doc) => {
+            return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${Services.Handwriting}`)).then(async response => {
+                let apiKey = await response.text();
+                if (!apiKey) {
+                    return undefined;
+                }
+
+                let xhttp = new XMLHttpRequest();
+                let serverAddress = "https://api.cognitive.microsoft.com";
+                let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+
+                let results = await new Promise<any>((resolve, reject) => {
+                    let result: any;
+                    xhttp.onreadystatechange = function () {
+                        if (this.readyState === 4) {
+                            try {
+                                result = JSON.parse(xhttp.responseText);
+                            } catch (e) {
+                                return reject(e);
+                            }
+                            switch (this.status) {
+                                case 200:
+                                    return resolve(result);
+                                case 400:
+                                default:
+                                    return reject(result);
+                            }
+                        }
+                    };
+
+                    xhttp.open("PUT", endpoint, true);
+                    xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
+                    xhttp.setRequestHeader('Content-Type', 'application/json');
+                    xhttp.send(JSON.stringify(toHandwritingUnit(inkData)));
+                });
+
+                let recognizedText = results.recognitionUnits.map((unit: any) => unit.recognizedText);
+                let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
+                target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results.recognitionUnits, "Ink Analysis");
+                target.handwriting = individualWords.join(" ");
+            });
+        };
+
+        const toHandwritingUnit = (inkData: InkData): HandwritingUnit => {
+            let entries = inkData.entries(), next = entries.next();
+            let strokes: AzureStrokeData[] = [];
+            let id = 0;
+            while (!next.done) {
+                let entry = next.value;
+                let data = {
+                    id: id++,
+                    points: entry[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+                    language: "en-US"
+                };
+                strokes.push(data);
+                next = entries.next();
+            }
+            return {
+                version: 1,
+                language: "en-US",
+                unit: "mm",
+                strokes: strokes
+            };
+        };
+
+    }
+
 }
\ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 3e0d7b476..e48b45b56 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -10,6 +10,8 @@ import { undoBatch, UndoManager } from "../util/UndoManager";
 import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { ContextMenu } from "./ContextMenu";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
@@ -178,7 +180,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
         return (
-            <div className="inkingCanvas" >
+            <div className="inkingCanvas">
                 <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
                 {this.props.children()}
                 {this.drawnPaths}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..53be0278e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -394,6 +394,7 @@ export class MainView extends React.Component {
             <div id="add-options-content">
                 <ul id="add-options-list">
                     <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
+                    <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
                     <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
                     <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
                     {btns.map(btn =>
@@ -444,7 +445,6 @@ export class MainView extends React.Component {
         this.isSearchVisible = !this.isSearchVisible;
     }
 
-
     render() {
         return (
             <div id="main-div">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 703873681..3fd86e950 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,6 +31,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 
 
 export const panZoomSchema = createSchema({
@@ -51,6 +52,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastY: number = 0;
     private get _pwidth() { return this.props.PanelWidth(); }
     private get _pheight() { return this.props.PanelHeight(); }
+    private inkKey = "ink";
 
     @computed get contentBounds() {
         let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
@@ -477,6 +479,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }, "arrange contents");
             }
         });
+        ContextMenu.Instance.addItem({
+            description: "Analyze Strokes", event: async () => {
+                let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
+                if (!data) {
+                    return;
+                }
+                CognitiveServices.Inking.analyze(data.inkData, Doc.GetProto(this.props.Document));
+            }
+        });
         ContextMenu.Instance.addItem({
             description: "Add freeform arrangement",
             event: () => {
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f80840f96..966ade3de 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -31,6 +31,8 @@ export interface PresViewProps {
     Documents: List<Doc>;
 }
 
+const expandedWidth = 400;
+
 @observer
 export class PresentationView extends React.Component<PresViewProps>  {
     public static Instance: PresentationView;
@@ -67,6 +69,14 @@ export class PresentationView extends React.Component<PresViewProps>  {
         PresentationView.Instance = this;
     }
 
+    toggle = (forcedValue: boolean | undefined) => {
+        if (forcedValue !== undefined) {
+            this.curPresentation.width = forcedValue ? expandedWidth : 0;
+        } else {
+            this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
+        }
+    }
+
     //The first lifecycle function that gets called to set up the current presentation.
     async componentWillMount() {
         this.props.Documents.forEach(async (doc, index: number) => {
@@ -543,7 +553,7 @@ export class PresentationView extends React.Component<PresViewProps>  {
             this.curPresentation.data = new List([doc]);
         }
 
-        this.curPresentation.width = 400;
+        this.toggle(true);
     }
 
     //Function that sets the store of the children docs.
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
index a436dcf2b..b5b3aa588 100644
--- a/src/new_fields/FieldSymbols.ts
+++ b/src/new_fields/FieldSymbols.ts
@@ -7,4 +7,4 @@ 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
+export const ToScriptString = Symbol("ToScriptString");
\ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 39c6c8ce3..8f64c1c2e 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -19,6 +19,8 @@ export interface StrokeData {
     page: number;
 }
 
+export type InkData = Map<string, StrokeData>;
+
 const pointSchema = createSimpleSchema({
     x: true, y: true
 });
@@ -31,9 +33,9 @@ const strokeDataSchema = createSimpleSchema({
 @Deserializable("ink")
 export class InkField extends ObjectField {
     @serializable(map(object(strokeDataSchema)))
-    readonly inkData: Map<string, StrokeData>;
+    readonly inkData: InkData;
 
-    constructor(data?: Map<string, StrokeData>) {
+    constructor(data?: InkData) {
         super();
         this.inkData = data || new Map;
     }
diff --git a/src/server/index.ts b/src/server/index.ts
index 5b086a2cf..66c982adc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -284,18 +284,15 @@ addSecureRoute(
     RouteStore.getCurrUser
 );
 
+const ServicesApiKeyMap = new Map<string, string | undefined>([
+    ["face", process.env.FACE],
+    ["vision", process.env.VISION],
+    ["handwriting", process.env.HANDWRITING]
+]);
+
 addSecureRoute(Method.GET, (user, res, req) => {
-    let requested = req.params.requestedservice;
-    switch (requested) {
-        case "face":
-            res.send(process.env.FACE);
-            break;
-        case "vision":
-            res.send(process.env.VISION);
-            break;
-        default:
-            res.send(undefined);
-    }
+    let service = req.params.requestedservice;
+    res.send(ServicesApiKeyMap.get(service));
 }, undefined, `${RouteStore.cognitiveServices}/:requestedservice`);
 
 class NodeCanvasFactory {
-- 
cgit v1.2.3-70-g09d2


From 52dc823c6a4c4b3e8deb994358a10acca4b6bd6c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 24 Jul 2019 12:31:07 -0400
Subject: Added transitions

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx    | 3 ---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx              | 3 ++-
 src/client/views/nodes/DocumentView.tsx                                | 1 -
 3 files changed, 2 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 70c43bbec..7a9b94bfd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -503,11 +503,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         onError(script.errors.map(error => error.messageText).join("\n"));
                         return;
                     }
-                    const docs = DocListCast(doc[dataKey]);
-                    docs.map(d => d.transition = "transform 1s");
                     doc[key] = new ScriptField(script);
                     overlayDisposer();
-                    setTimeout(() => docs.map(d => d.transition = undefined), 1200);
                 }} />;
                 overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
             };
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b09538d1a..389b9f68b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -82,6 +82,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     render() {
+        const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -90,7 +91,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     backgroundColor: "transparent",
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
-                    transition: StrCast(this.props.Document.transition),
+                    transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
                     width: this.width,
                     height: this.height,
                     zIndex: this.Document.zIndex || 0,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0a3633da2..138d065c6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -565,7 +565,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
         }
         cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
-        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         if (!ClientUtils.RELEASE) {
-- 
cgit v1.2.3-70-g09d2


From 4c9c8375dc5f36f29cc38ba79b379b7a75e4a1a5 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 24 Jul 2019 17:24:28 -0400
Subject: Added custom views to layout scripts

---
 .vscode/launch.json                                |  8 ++++++
 src/client/views/ScriptingRepl.tsx                 | 12 +++-----
 .../collectionFreeForm/CollectionFreeFormView.scss |  5 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 ++++++++++++++++++++--
 4 files changed, 48 insertions(+), 10 deletions(-)

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

diff --git a/.vscode/launch.json b/.vscode/launch.json
index e4196600e..fd67a7647 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,6 +7,10 @@
         {
             "type": "chrome",
             "request": "launch",
+            "runtimeArgs": [
+                "--enable-logging",
+                "--v=1"
+            ],
             "name": "Launch Chrome against localhost",
             "sourceMaps": true,
             "breakOnLoad": true,
@@ -25,6 +29,10 @@
         {
             "type": "chrome",
             "request": "launch",
+            "runtimeArgs": [
+                "--enable-logging",
+                "--v=1"
+            ],
             "name": "Launch Chrome against Dash server",
             "sourceMaps": true,
             "breakOnLoad": true,
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 52273c357..9e538cf1b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -16,18 +16,14 @@ library.add(faCaretRight);
 @observer
 export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
     render() {
-        this.props.view.props.ScreenToLocalTransform();
-        const doc = this.props.view.props.Document;
-        doc.width;
-        doc.height;
-        doc.x;
-        doc.y;
-        const screenCoords = this.props.view.screenRect();
+        const view = this.props.view;
+        const transform = view.props.ScreenToLocalTransform().scale(view.props.ContentScaling()).inverse();
+        const { x, y, width, height } = transform.transformBounds(0, 0, view.props.PanelWidth(), view.props.PanelHeight());
 
         return (
             <div className="documentIcon-outerDiv" style={{
                 position: "absolute",
-                transform: `translate(${screenCoords.left + screenCoords.width / 2}px, ${screenCoords.top}px)`,
+                transform: `translate(${x + width / 2}px, ${y}px)`,
             }}>
                 <p>${this.props.index}</p>
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 00407d39a..cca199afa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -19,6 +19,11 @@
     transform-origin: left top;
 }
 
+.collectionFreeform-customText {
+    position: absolute;
+    text-align: center;
+}
+
 .collectionfreeformview-container {
     .collectionfreeformview>.jsx-parser {
         position: inherit;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 840f253ff..bd5c6f84b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -420,6 +420,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return result.result === undefined ? {} : result.result;
     }
 
+    private viewDefToJSX(viewDef: any): JSX.Element | undefined {
+        if (viewDef.type === "text") {
+            const text = Cast(viewDef.text, "string");
+            const x = Cast(viewDef.x, "number");
+            const y = Cast(viewDef.y, "number");
+            const width = Cast(viewDef.width, "number");
+            const height = Cast(viewDef.height, "number");
+            const fontSize = Cast(viewDef.fontSize, "number");
+            if ([text, x, y].some(val => val === undefined)) {
+                return undefined;
+            }
+
+            return <div className="collectionFreeform-customText" style={{
+                transform: `translate(${x}px, ${y}px)`,
+                width, height, fontSize
+            }}>{text}</div>;
+        }
+    }
+
     @computed.struct
     get views() {
         let curPage = FieldValue(this.Document.curPage, -1);
@@ -427,10 +446,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const script = this.Document.arrangeScript;
         let state: any = undefined;
         const docs = this.childDocs;
+        let elements: JSX.Element[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
-                state = initResult.result;
+                const result = initResult.result;
+                const { state: scriptState, views } = result;
+                state = scriptState;
+                if (Array.isArray(views)) {
+                    elements = views.reduce<JSX.Element[]>((prev, ele) => {
+                        const jsx = this.viewDefToJSX(ele);
+                        jsx && prev.push(jsx);
+                        return prev;
+                    }, elements);
+                }
             }
         }
         let docviews = docs.reduce((prev, doc) => {
@@ -445,7 +474,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
             }
             return prev;
-        }, [] as JSX.Element[]);
+        }, elements);
 
         setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
 
-- 
cgit v1.2.3-70-g09d2


From d6cef0815815c2587b9cc791e6a37c742aba1b45 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 03:31:34 -0400
Subject: cognitive services refactor, buxton python script fixes, covered up
 imagebox context menu bug

---
 src/client/cognitive_services/CognitiveServices.ts | 151 +++++++++++----------
 src/client/views/InkingCanvas.tsx                  |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/scraping/buxton/scraper.py                     |  17 +--
 5 files changed, 93 insertions(+), 87 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index dcd27f858..d689b424d 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,5 +1,5 @@
 import * as request from "request-promise";
-import { Doc, Field } from "../../new_fields/Doc";
+import { Doc, Field, Opt } from "../../new_fields/Doc";
 import { Cast } from "../../new_fields/Types";
 import { ImageField } from "../../new_fields/URLField";
 import { List } from "../../new_fields/List";
@@ -10,7 +10,15 @@ import { CompileScript } from "../util/Scripting";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { InkData } from "../../new_fields/InkField";
 
-export enum Services {
+type APIManager<D> = { requester: RequestExecutor<D>, applier: AnalysisApplier };
+type RequestExecutor<D> = (apiKey: string, data: D, service: Service) => Promise<string>;
+type AnalysisApplier = (target: Doc, ...args: any) => any;
+type Converter = (results: any) => Field;
+
+export type Tag = { name: string, confidence: number };
+export type Rectangle = { top: number, left: number, width: number, height: number };
+
+export enum Service {
     ComputerVision = "vision",
     Face = "face",
     Handwriting = "handwriting"
@@ -25,11 +33,6 @@ export enum Confidence {
     Excellent = 0.95
 }
 
-export type Tag = { name: string, confidence: number };
-export type Rectangle = { top: number, left: number, width: number, height: number };
-export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
-export type Converter = (results: any) => Field;
-
 /**
  * A file that handles all interactions with Microsoft Azure's Cognitive
  * Services APIs. These machine learning endpoints allow basic data analytics for
@@ -37,19 +40,33 @@ export type Converter = (results: any) => Field;
  */
 export namespace CognitiveServices {
 
+    const executeQuery = async <D, R>(service: Service, executor: RequestExecutor<D>, data: D): Promise<Opt<R>> => {
+        return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
+            let apiKey = await response.text();
+            if (!apiKey) {
+                return undefined;
+            }
+
+            let results: Opt<R>;
+            try {
+                results = await executor(apiKey, data, service).then(json => JSON.parse(json));
+            } catch {
+                results = undefined;
+            }
+            return results;
+        });
+    };
+
     export namespace Image {
 
-        export const analyze = async (imageUrl: string, service: Services) => {
-            return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
-                let apiKey = await response.text();
-                if (!apiKey) {
-                    return undefined;
-                }
+        export const Manager: APIManager<string> = {
+
+            requester: (async (apiKey: string, imageUrl: string, service: Service) => {
                 let uriBase;
                 let parameters;
 
                 switch (service) {
-                    case Services.Face:
+                    case Service.Face:
                         uriBase = 'face/v1.0/detect';
                         parameters = {
                             'returnFaceId': 'true',
@@ -58,7 +75,7 @@ export namespace CognitiveServices {
                                 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise'
                         };
                         break;
-                    case Services.ComputerVision:
+                    case Service.ComputerVision:
                         uriBase = 'vision/v2.0/analyze';
                         parameters = {
                             'visualFeatures': 'Categories,Description,Color,Objects,Tags,Adult',
@@ -78,35 +95,32 @@ export namespace CognitiveServices {
                     }
                 };
 
-                let results: any;
-                try {
-                    results = await request.post(options).then(response => JSON.parse(response));
-                } catch (e) {
-                    results = undefined;
-                }
-                return results;
-            });
-        };
+                return request.post(options);
+            }) as RequestExecutor<string>,
 
-        const analyzeDocument = async (target: Doc, service: Services, converter: Converter, storageKey: string) => {
-            let imageData = Cast(target.data, ImageField);
-            if (!imageData || await Cast(target[storageKey], Doc)) {
-                return;
-            }
-            let toStore: any;
-            let results = await analyze(imageData.url.href, service);
-            if (!results) {
-                toStore = "Cognitive Services could not process the given image URL.";
-            } else {
-                if (!results.length) {
-                    toStore = converter(results);
+            applier: (async (target: Doc, service: Service, converter: Converter, storageKey: string) => {
+                let imageData = Cast(target.data, ImageField);
+                if (!imageData || await Cast(target[storageKey], Doc)) {
+                    return;
+                }
+                let toStore: any;
+                let results = await executeQuery<string, any>(service, Manager.requester, imageData.url.href);
+                if (!results) {
+                    toStore = "Cognitive Services could not process the given image URL.";
                 } else {
-                    toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+                    if (!results.length) {
+                        toStore = converter(results);
+                    } else {
+                        toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+                    }
                 }
-            }
-            target[storageKey] = toStore;
+                target[storageKey] = toStore;
+            }) as AnalysisApplier
+
         };
 
+        export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
+
         export const generateMetadata = async (target: Doc, threshold: Confidence = Confidence.Excellent) => {
             let converter = (results: any) => {
                 let tagDoc = new Doc;
@@ -120,7 +134,7 @@ export namespace CognitiveServices {
                 tagDoc.confidence = threshold;
                 return tagDoc;
             };
-            analyzeDocument(target, Services.ComputerVision, converter, "generatedTags");
+            Manager.applier(target, Service.ComputerVision, converter, "generatedTags");
         };
 
         export const extractFaces = async (target: Doc) => {
@@ -129,48 +143,24 @@ export namespace CognitiveServices {
                 results.map((face: Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!));
                 return faceDocs;
             };
-            analyzeDocument(target, Services.Face, converter, "faces");
+            Manager.applier(target, Service.Face, converter, "faces");
         };
 
     }
 
     export namespace Inking {
 
-        export interface AzureStrokeData {
-            id: number;
-            points: string;
-            language?: string;
-        }
-
-        export interface HandwritingUnit {
-            version: number;
-            language: string;
-            unit: string;
-            strokes: AzureStrokeData[];
-        }
-
-        export const analyze = async (inkData: InkData, target: Doc) => {
-            return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${Services.Handwriting}`)).then(async response => {
-                let apiKey = await response.text();
-                if (!apiKey) {
-                    return undefined;
-                }
+        export const Manager: APIManager<InkData> = {
 
+            requester: (async (apiKey: string, inkData: InkData) => {
                 let xhttp = new XMLHttpRequest();
                 let serverAddress = "https://api.cognitive.microsoft.com";
                 let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
 
-                let requestExecutor = (resolve: any, reject: any) => {
-                    let result: any;
-                    let body = format(inkData);
-
+                let promisified = (resolve: any, reject: any) => {
                     xhttp.onreadystatechange = function () {
                         if (this.readyState === 4) {
-                            try {
-                                result = JSON.parse(xhttp.responseText);
-                            } catch (e) {
-                                return reject(e);
-                            }
+                            let result = xhttp.responseText;
                             switch (this.status) {
                                 case 200:
                                     return resolve(result);
@@ -184,11 +174,14 @@ export namespace CognitiveServices {
                     xhttp.open("PUT", endpoint, true);
                     xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                     xhttp.setRequestHeader('Content-Type', 'application/json');
-                    xhttp.send(body);
+                    xhttp.send(format(inkData));
                 };
 
-                let results = await new Promise<any>(requestExecutor);
+                return new Promise<any>(promisified);
+            }) as RequestExecutor<InkData>,
 
+            applier: (async (target: Doc, inkData: InkData) => {
+                let results = await executeQuery<InkData, any>(Service.Handwriting, Manager.requester, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
                     target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
@@ -196,9 +189,23 @@ export namespace CognitiveServices {
                     let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
                     target.handwriting = individualWords.join(" ");
                 }
-            });
+            }) as AnalysisApplier
+
         };
 
+        export interface AzureStrokeData {
+            id: number;
+            points: string;
+            language?: string;
+        }
+
+        export interface HandwritingUnit {
+            version: number;
+            language: string;
+            unit: string;
+            strokes: AzureStrokeData[];
+        }
+
         const format = (inkData: InkData): string => {
             let entries = inkData.entries(), next = entries.next();
             let strokes: AzureStrokeData[] = [], id = 0;
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index e48b45b56..c4cd863d1 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -6,12 +6,10 @@ import "./InkingCanvas.scss";
 import { InkingControl } from "./InkingControl";
 import { InkingStroke } from "./InkingStroke";
 import React = require("react");
-import { undoBatch, UndoManager } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
 import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { ContextMenu } from "./ContextMenu";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bd5c6f84b..4b7ddd49d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -519,7 +519,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 if (!data) {
                     return;
                 }
-                CognitiveServices.Inking.analyze(data.inkData, Doc.GetProto(this.props.Document));
+                CognitiveServices.Inking.Manager.applier(Doc.GetProto(this.props.Document), data.inkData);
             }
         });
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 89ad18dd6..3d77696fe 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -216,9 +216,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             });
 
             let modes: ContextMenuProps[] = [];
-            let dataDoc = Doc.GetProto(this.Document);
-            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
-            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
+            // let dataDoc = Doc.GetProto(this.Document);
+            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(this.Document), icon: "tag" });
+            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(this.Document), icon: "camera" });
 
             ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
             ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 700269727..14490cfe4 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -15,6 +15,7 @@ source = "./source"
 dist = "../../server/public/files"
 
 db = MongoClient("localhost", 27017)["Dash"]
+target_collection = db.newDocuments
 schema_guids = []
 
 
@@ -84,7 +85,7 @@ def write_schema(parse_results, display_fields, storage_key):
             "height": 600,
             "panX": 0,
             "panY": 0,
-            "zoomBasis": 0.5,
+            "zoomBasis": 1,
             "zIndex": 2,
             "libraryBrush": False,
             "viewType": 2
@@ -106,8 +107,8 @@ def write_schema(parse_results, display_fields, storage_key):
     fields["isPrototype"] = True
     fields["page"] = -1
 
-    db.newDocuments.insert_one(data_doc)
-    db.newDocuments.insert_one(view_doc)
+    target_collection.insert_one(data_doc)
+    target_collection.insert_one(view_doc)
 
     data_doc_guid = data_doc["_id"]
     print(f"inserted view document ({view_doc_guid})")
@@ -158,8 +159,8 @@ def write_text_doc(content):
         "__type": "Doc"
     }
 
-    db.newDocuments.insert_one(view_doc)
-    db.newDocuments.insert_one(data_doc)
+    target_collection.insert_one(view_doc)
+    target_collection.insert_one(data_doc)
 
     return view_doc_guid
 
@@ -209,8 +210,8 @@ def write_image(folder, name):
         "__type": "Doc"
     }
 
-    db.newDocuments.insert_one(view_doc)
-    db.newDocuments.insert_one(data_doc)
+    target_collection.insert_one(view_doc)
+    target_collection.insert_one(data_doc)
 
     return view_doc_guid
 
@@ -372,7 +373,7 @@ parent_guid = write_schema({
 }, ["title", "short_description", "original_price"], "data")
 
 print("appending parent schema to main workspace...\n")
-db.newDocuments.update_one(
+target_collection.update_one(
     {"fields.title": "WS collection 1"},
     {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}}
 )
-- 
cgit v1.2.3-70-g09d2


From 1846c217adb814a2b0297b8ab808c2f587741936 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 09:26:33 -0400
Subject: final cognitive services refactor

---
 src/client/cognitive_services/CognitiveServices.ts | 84 +++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 2 files changed, 47 insertions(+), 41 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d689b424d..0cda17325 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -10,9 +10,10 @@ import { CompileScript } from "../util/Scripting";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { InkData } from "../../new_fields/InkField";
 
-type APIManager<D> = { requester: RequestExecutor<D>, applier: AnalysisApplier };
-type RequestExecutor<D> = (apiKey: string, data: D, service: Service) => Promise<string>;
-type AnalysisApplier = (target: Doc, ...args: any) => any;
+type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor, analyzer: AnalysisApplier };
+type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
+type AnalysisApplier = (target: Doc, relevantKeys: string[], ...args: any) => any;
+type BodyConverter<D> = (data: D) => string;
 type Converter = (results: any) => Field;
 
 export type Tag = { name: string, confidence: number };
@@ -40,7 +41,7 @@ export enum Confidence {
  */
 export namespace CognitiveServices {
 
-    const executeQuery = async <D, R>(service: Service, executor: RequestExecutor<D>, data: D): Promise<Opt<R>> => {
+    const executeQuery = async <D, R>(service: Service, manager: APIManager<D>, data: D): Promise<Opt<R>> => {
         return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
             let apiKey = await response.text();
             if (!apiKey) {
@@ -49,7 +50,7 @@ export namespace CognitiveServices {
 
             let results: Opt<R>;
             try {
-                results = await executor(apiKey, data, service).then(json => JSON.parse(json));
+                results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json));
             } catch {
                 results = undefined;
             }
@@ -61,7 +62,9 @@ export namespace CognitiveServices {
 
         export const Manager: APIManager<string> = {
 
-            requester: (async (apiKey: string, imageUrl: string, service: Service) => {
+            converter: (imageUrl: string) => JSON.stringify({ url: imageUrl }),
+
+            requester: async (apiKey: string, body: string, service: Service) => {
                 let uriBase;
                 let parameters;
 
@@ -88,7 +91,7 @@ export namespace CognitiveServices {
                 const options = {
                     uri: 'https://eastus.api.cognitive.microsoft.com/' + uriBase,
                     qs: parameters,
-                    body: `{"url": "${imageUrl}"}`,
+                    body: body,
                     headers: {
                         'Content-Type': 'application/json',
                         'Ocp-Apim-Subscription-Key': apiKey
@@ -96,15 +99,16 @@ export namespace CognitiveServices {
                 };
 
                 return request.post(options);
-            }) as RequestExecutor<string>,
+            },
 
-            applier: (async (target: Doc, service: Service, converter: Converter, storageKey: string) => {
+            analyzer: async (target: Doc, keys: string[], service: Service, converter: Converter) => {
                 let imageData = Cast(target.data, ImageField);
+                let storageKey = keys[0];
                 if (!imageData || await Cast(target[storageKey], Doc)) {
                     return;
                 }
                 let toStore: any;
-                let results = await executeQuery<string, any>(service, Manager.requester, imageData.url.href);
+                let results = await executeQuery<string, any>(service, Manager, imageData.url.href);
                 if (!results) {
                     toStore = "Cognitive Services could not process the given image URL.";
                 } else {
@@ -115,7 +119,7 @@ export namespace CognitiveServices {
                     }
                 }
                 target[storageKey] = toStore;
-            }) as AnalysisApplier
+            }
 
         };
 
@@ -134,7 +138,7 @@ export namespace CognitiveServices {
                 tagDoc.confidence = threshold;
                 return tagDoc;
             };
-            Manager.applier(target, Service.ComputerVision, converter, "generatedTags");
+            Manager.analyzer(target, ["generatedTags"], Service.ComputerVision, converter);
         };
 
         export const extractFaces = async (target: Doc) => {
@@ -143,7 +147,7 @@ export namespace CognitiveServices {
                 results.map((face: Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!));
                 return faceDocs;
             };
-            Manager.applier(target, Service.Face, converter, "faces");
+            Manager.analyzer(target, ["faces"], Service.Face, converter);
         };
 
     }
@@ -152,7 +156,26 @@ export namespace CognitiveServices {
 
         export const Manager: APIManager<InkData> = {
 
-            requester: (async (apiKey: string, inkData: InkData) => {
+            converter: (inkData: InkData): string => {
+                let entries = inkData.entries(), next = entries.next();
+                let strokes: AzureStrokeData[] = [], id = 0;
+                while (!next.done) {
+                    strokes.push({
+                        id: id++,
+                        points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+                        language: "en-US"
+                    });
+                    next = entries.next();
+                }
+                return JSON.stringify({
+                    version: 1,
+                    language: "en-US",
+                    unit: "mm",
+                    strokes: strokes
+                });
+            },
+
+            requester: async (apiKey: string, body: string) => {
                 let xhttp = new XMLHttpRequest();
                 let serverAddress = "https://api.cognitive.microsoft.com";
                 let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
@@ -174,22 +197,22 @@ export namespace CognitiveServices {
                     xhttp.open("PUT", endpoint, true);
                     xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                     xhttp.setRequestHeader('Content-Type', 'application/json');
-                    xhttp.send(format(inkData));
+                    xhttp.send(body);
                 };
 
                 return new Promise<any>(promisified);
-            }) as RequestExecutor<InkData>,
+            },
 
-            applier: (async (target: Doc, inkData: InkData) => {
-                let results = await executeQuery<InkData, any>(Service.Handwriting, Manager.requester, inkData);
+            analyzer: async (target: Doc, keys: string[], inkData: InkData) => {
+                let results = await executeQuery<InkData, any>(Service.Handwriting, Manager, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
-                    target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
+                    target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
                     let recognizedText = results.map((item: any) => item.recognizedText);
                     let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
-                    target.handwriting = individualWords.join(" ");
+                    target[keys[1]] = individualWords.join(" ");
                 }
-            }) as AnalysisApplier
+            }
 
         };
 
@@ -206,25 +229,6 @@ export namespace CognitiveServices {
             strokes: AzureStrokeData[];
         }
 
-        const format = (inkData: InkData): string => {
-            let entries = inkData.entries(), next = entries.next();
-            let strokes: AzureStrokeData[] = [], id = 0;
-            while (!next.done) {
-                strokes.push({
-                    id: id++,
-                    points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
-                    language: "en-US"
-                });
-                next = entries.next();
-            }
-            return JSON.stringify({
-                version: 1,
-                language: "en-US",
-                unit: "mm",
-                strokes: strokes
-            });
-        };
-
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b7ddd49d..d39d6f255 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -519,7 +519,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 if (!data) {
                     return;
                 }
-                CognitiveServices.Inking.Manager.applier(Doc.GetProto(this.props.Document), data.inkData);
+                let target = Doc.GetProto(this.props.Document);
+                let relevantKeys = ["inkAnalysis", "handwriting"];
+                CognitiveServices.Inking.Manager.analyzer(target, relevantKeys, data.inkData);
             }
         });
     }
-- 
cgit v1.2.3-70-g09d2


From b4888c53bcc4eb9a37ed4b3dac740bfffc64d3c5 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 11:46:28 -0400
Subject: postponed dealing with circular import bug imagebox

---
 src/client/DocServer.ts                                             | 1 -
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 ++++-
 src/client/views/nodes/ImageBox.tsx                                 | 6 +++---
 3 files changed, 7 insertions(+), 5 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 077c8e5ba..df9828029 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,7 +5,6 @@ import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
-import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d39d6f255..701574cfc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,10 @@ import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
 
+library.add(faEye);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -522,7 +525,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let target = Doc.GetProto(this.props.Document);
                 let relevantKeys = ["inkAnalysis", "handwriting"];
                 CognitiveServices.Inking.Manager.analyzer(target, relevantKeys, data.inkData);
-            }
+            }, icon: "eye"
         });
     }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3d77696fe..c0c64b6d5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -216,9 +216,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             });
 
             let modes: ContextMenuProps[] = [];
-            // let dataDoc = Doc.GetProto(this.Document);
-            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(this.Document), icon: "tag" });
-            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(this.Document), icon: "camera" });
+            let dataDoc = Doc.GetProto(this.props.Document);
+            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
+            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
 
             ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
             ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
-- 
cgit v1.2.3-70-g09d2


From 6c111fdc538775f9f7b04f91d0b5701fd2a1aaca Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 12:59:41 -0400
Subject: icon and stop prevention of undo redo propagation

---
 src/client/views/GlobalKeyHandler.ts                                  | 4 +++-
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 +++-
 2 files changed, 6 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e8a588e58..7477c5b4f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,4 +1,4 @@
-import { UndoManager, undoBatch } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { CollectionDockingView } from "./collections/CollectionDockingView";
 import { MainView } from "./MainView";
@@ -144,9 +144,11 @@ export default class KeyManager {
                 break;
             case "y":
                 UndoManager.Redo();
+                stopPropagation = false;
                 break;
             case "z":
                 UndoManager.Undo();
+                stopPropagation = false;
                 break;
             case "a":
             case "c":
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 701574cfc..15734ce0d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,8 +34,9 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faTable } from "@fortawesome/free-solid-svg-icons";
 
-library.add(faEye);
+library.add(faEye, faTable);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -492,6 +493,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onContextMenu = () => {
         ContextMenu.Instance.addItem({
             description: "Arrange contents in grid",
+            icon: "table",
             event: async () => {
                 const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
                 UndoManager.RunInBatch(() => {
-- 
cgit v1.2.3-70-g09d2


From 8e567d94618fcac3345a1f495cdea71a543f7804 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 15:09:24 -0400
Subject: added misc icons and updated metadata storange onto field extension
 doc

---
 src/client/views/InkingControl.tsx                            |  2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx |  9 ++++-----
 src/client/views/nodes/ImageBox.tsx                           | 11 ++++++-----
 3 files changed, 11 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 1910e409b..da388e532 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -20,7 +20,7 @@ export class InkingControl extends React.Component {
     static Instance: InkingControl = new InkingControl({});
     @observable private _selectedTool: InkTool = InkTool.None;
     @observable private _selectedColor: string = "rgb(244, 67, 54)";
-    @observable private _selectedWidth: string = "25";
+    @observable private _selectedWidth: string = "5";
     @observable public _open: boolean = false;
 
     constructor(props: Readonly<{}>) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 15734ce0d..6500b3273 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,9 +34,9 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable } from "@fortawesome/free-solid-svg-icons";
+import { faTable, faPaintBrush, faAsterisk } from "@fortawesome/free-solid-svg-icons";
 
-library.add(faEye, faTable);
+library.add(faEye, faTable, faPaintBrush);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -524,10 +524,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 if (!data) {
                     return;
                 }
-                let target = Doc.GetProto(this.props.Document);
                 let relevantKeys = ["inkAnalysis", "handwriting"];
-                CognitiveServices.Inking.Manager.analyzer(target, relevantKeys, data.inkData);
-            }, icon: "eye"
+                CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData);
+            }, icon: "paint-brush"
         });
     }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c0c64b6d5..5e93251a3 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faImage, faFileAudio } from '@fortawesome/free-solid-svg-icons';
+import { faImage, faFileAudio, faPaintBrush, faAsterisk } from '@fortawesome/free-solid-svg-icons';
 import { action, observable, computed, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
@@ -27,13 +27,14 @@ import { Font } from '@react-pdf/renderer';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
 import FaceRectangles from './FaceRectangles';
+import { faEye } from '@fortawesome/free-regular-svg-icons';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl, Howler } = require('howler');
 
 
-library.add(faImage);
-library.add(faFileAudio);
+library.add(faImage, faEye, faPaintBrush);
+library.add(faFileAudio, faAsterisk);
 
 
 export const pageSchema = createSchema({
@@ -220,8 +221,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
             modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
 
-            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
-            ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
+            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" });
+            ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" });
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 7d468c19c28a48314ea993d84e5995364e88fa86 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 26 Jul 2019 02:21:09 -0400
Subject: added a MakeBackground and FitToBox menu items.   fixed problems with
 fitToBox and frozen dimensions.  fixed issue with drop() on free forms.

---
 .../views/collections/CollectionBaseView.scss      |  2 +-
 .../views/collections/CollectionBaseView.tsx       |  6 ++--
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 32 +++++++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentView.scss           |  1 -
 src/client/views/nodes/DocumentView.tsx            |  7 +++++
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 8 files changed, 37 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss
index 34bcb705e..583e6f6ca 100644
--- a/src/client/views/collections/CollectionBaseView.scss
+++ b/src/client/views/collections/CollectionBaseView.scss
@@ -6,7 +6,7 @@
     border-radius: 0 0 $border-radius $border-radius;
     box-sizing: border-box;
     border-radius: inherit;
-    pointer-events: all;
     width:100%;
     height:100%;
+    overflow: auto;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c39caec24..67112ae7c 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -126,7 +126,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let self = this;
         let targetDataDoc = this.props.Document;
         if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) {
-            //if (Doc.AreProtosEqual(this.extensionDoc, targetCollection)) {
             return true;
         }
         if (this.removeDocument(doc)) {
@@ -146,7 +145,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         const viewtype = this.collectionViewType;
         return (
             <div id="collectionBaseView"
-                style={{ overflow: "auto", boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}` }}
+                style={{
+                    pointerEvents: this.props.Document.isBackground ? "none" : "all",
+                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
+                }}
                 className={this.props.className || "collectionView-cont"}
                 onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2ddefb3c0..cbc9e61eb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -104,7 +104,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 } else if (de.data.moveDocument) {
                     let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
                     added = movedDocs.reduce((added: boolean, d) =>
-                        de.data.moveDocument(d, /*this.props.DataDoc ? this.props.DataDoc :*/ this.props.Document, this.props.addDocument) || added, false);
+                        de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
                 } else {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6500b3273..9c4275388 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,9 +34,11 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable, faPaintBrush, faAsterisk } from "@fortawesome/free-solid-svg-icons";
+import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt } from "@fortawesome/free-solid-svg-icons";
+import { undo } from "prosemirror-history";
+import { number } from "prop-types";
 
-library.add(faEye, faTable, faPaintBrush);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -58,24 +60,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private get _pheight() { return this.props.PanelHeight(); }
     private inkKey = "ink";
 
+    get prevScaling() {
+        return (this.props as any).ContentScaling && this.Document.nativeWidth && this.fitToBox ? (this.props as any).ContentScaling() : 1;
+    }
+
     @computed get contentBounds() {
-        let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
+        let bounds = (this.fitToBox) && !this.nativeWidth ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
         return {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
-            scale: bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1
+            scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.prevScaling
         };
     }
 
-    @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+    @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; }
+    @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; }
     public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private panX = () => this.contentBounds.panX;
     private panY = () => this.contentBounds.panY;
     private zoomScaling = () => this.contentBounds.scale;
-    private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0;  // shift so pan position is at center of window for non-overlay collections
-    private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
+    private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 / this.prevScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 / this.prevScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
@@ -109,11 +116,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
+        let xf = this.getTransform();
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let dragDoc = de.data.droppedDocuments[0];
-                    let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+                    let [xp, yp] = xf.transformPoint(de.x, de.y);
                     let x = xp - de.data.xOffset;
                     let y = yp - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
@@ -491,6 +498,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     onContextMenu = () => {
+        ContextMenu.Instance.addItem({
+            description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`,
+            event: undoBatch(async () => this.props.Document.fitToBox = !this.fitToBox),
+            icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         ContextMenu.Instance.addItem({
             description: "Arrange contents in grid",
             icon: "table",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b765517a2..d96e93aeb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -365,7 +365,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     marqueeSelect() {
         let selRect = this.Bounds;
         let selection: Doc[] = [];
-        this.props.activeDocuments().map(doc => {
+        this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
             var z = NumCast(doc.zoomBasis, 1);
             var x = NumCast(doc.x);
             var y = NumCast(doc.y);
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 3a4b46b7e..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -4,7 +4,6 @@
   position: inherit;
   top: 0;
   left:0;
-  pointer-events: all;
  // background: $light-color; //overflow: hidden;
   transform-origin: left top;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4a5e20533..9d2dd3121 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -514,6 +514,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false);
     }
+    @undoBatch
+    @action
+    makeBackground = (): void => {
+        this.props.Document.isBackground = true;
+    }
 
     @undoBatch
     @action
@@ -544,6 +549,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+        cm.addItem({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Make Portal", event: () => {
@@ -648,6 +654,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
+                    pointerEvents: this.props.Document.isBackground ? "none" : "all",
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ea167286b..a36885616 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -350,7 +350,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         if (field instanceof ImageField) paths = [this.choosePath(field.url)];
         paths.push(...altpaths);
         // }
-        let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
+        let interactive = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "" : "-interactive";
         let rotation = NumCast(this.dataDoc.rotation, 0);
         let aspect = (rotation % 180) ? this.dataDoc[HeightSym]() / this.dataDoc[WidthSym]() : 1;
         let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
-- 
cgit v1.2.3-70-g09d2


From 04030194e690764a2d1a58b25568511a3c6dcb98 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 26 Jul 2019 09:14:06 -0400
Subject: fixed fitToBox layout issue

---
 .../collectionFreeForm/CollectionFreeFormView.tsx            | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9c4275388..5d61aefc7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -60,16 +60,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private get _pheight() { return this.props.PanelHeight(); }
     private inkKey = "ink";
 
-    get prevScaling() {
-        return (this.props as any).ContentScaling && this.Document.nativeWidth && this.fitToBox ? (this.props as any).ContentScaling() : 1;
+    get parentScaling() {
+        return (this.props as any).ContentScaling && this.Document.nativeWidth && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
     }
 
     @computed get contentBounds() {
-        let bounds = (this.fitToBox) && !this.nativeWidth ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
+        let bounds = this.fitToBox && !this.nativeWidth && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
         return {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
-            scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.prevScaling
+            scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling
         };
     }
 
@@ -81,8 +81,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private panX = () => this.contentBounds.panX;
     private panY = () => this.contentBounds.panY;
     private zoomScaling = () => this.contentBounds.scale;
-    private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 / this.prevScaling : 0;  // shift so pan position is at center of window for non-overlay collections
-    private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 / this.prevScaling : 0;// shift so pan position is at center of window for non-overlay collections
+    private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
-- 
cgit v1.2.3-70-g09d2


From 5b70c7f5562821231665c4058d788bbf3b41ff04 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 26 Jul 2019 17:46:11 -0400
Subject: Changed render depth for overlay and background layouts

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 src/client/views/nodes/ImageBox.tsx                                     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6500b3273..c711b5204 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -400,7 +400,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
-            renderDepth: this.props.renderDepth + 1,
+            renderDepth: this.props.renderDepth,
             selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6541007d0..c691754ea 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -248,7 +248,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             return url.href;
         }
         let ext = path.extname(url.href);
-        const suffix = this.props.renderDepth <= 1 ? "_o" : this._curSuffix;
+        const suffix = this.props.renderDepth < 1 ? "_o" : this._curSuffix;
         return url.href.replace(ext, suffix + ext);
     }
 
-- 
cgit v1.2.3-70-g09d2


From cc806ebbe8d48d1c5a4c3c49231a5d38d6f39943 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 27 Jul 2019 04:12:07 -0400
Subject: fixed a bunch of template and stacking view issues among others.

---
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/GlobalKeyHandler.ts               |  1 +
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 39 +++++--------
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 68 +++++++++-------------
 src/client/views/collections/CollectionView.tsx    |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 22 +++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 16 +----
 src/client/views/nodes/DocumentView.tsx            | 14 +++--
 src/new_fields/Doc.ts                              | 13 ++++-
 11 files changed, 75 insertions(+), 108 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 255855b45..c08a84742 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -533,7 +533,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     proto.nativeHeight = nheight = doc.height || 0;
                     proto.ignoreAspect = true;
                 }
-                if (nwidth > 0 && nheight > 0) {
+                if (nwidth > 0 && nheight > 0 && !BoolCast(proto.ignoreAspect)) {
                     if (Math.abs(dW) > Math.abs(dH)) {
                         if (!fixedAspect) {
                             Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true);
@@ -553,7 +553,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 } else {
                     dW && (doc.width = actualdW);
                     dH && (doc.height = actualdH);
-                    Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
+                    dH && Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
                 }
             }
         });
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 7477c5b4f..5050f34ab 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -67,6 +67,7 @@ export default class KeyManager {
                     }
                 }
                 MainView.Instance.toggleColorPicker(true);
+                SelectionManager.DeselectAll();
                 break;
             case "delete":
             case "backspace":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 61a013963..bfb50bc75 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -407,7 +407,7 @@ export class MainView extends React.Component {
                             </button>
                         </div></li>)}
                     <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
-                    <li key="color"><button className="add-button round-button" title="Select Color" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
+                    <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
                         <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
                             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
                         </div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ba7903419..0865058be 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -539,17 +539,17 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
 
+    panelWidth = () => Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth()));
+    panelHeight = () => Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), NumCast(this._document!.nativeHeight, this._panelHeight)));
+
+    nativeWidth = () => !BoolCast(this._document!.ignoreAspect) ? NumCast(this._document!.nativeWidth, this._panelWidth) : 0;
+    nativeHeight = () => !BoolCast(this._document!.ignoreAspect) ? NumCast(this._document!.nativeHeight, this._panelHeight) : 0;
 
-    nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
-    nativeHeight = () => {
-        let nh = NumCast(this._document!.nativeHeight, this._panelHeight);
-        let res = BoolCast(this._document!.ignoreAspect) ? this._panelHeight : nh;
-        return res;
-    }
     contentScaling = () => {
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
-        let wscale = this._panelWidth / nativeW;
+        if (!nativeW || !nativeH) return 1;
+        let wscale = this.panelWidth() / nativeW;
         return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
     }
 
@@ -561,18 +561,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
         return Transform.Identity();
     }
-    get scaleToFitMultiplier() {
-        let docWidth = NumCast(this._document!.width);
-        let docHeight = NumCast(this._document!.height);
-        if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1;
-        if (StrCast(this._document!.layout).indexOf("Collection") === -1 ||
-            !BoolCast(this._document!.fitToContents, false) ||
-            NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1;
-        let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ?
-            this._panelHeight / docHeight : this._panelWidth / docWidth);
-        return scaling;
-    }
-    get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
+    get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
 
     addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
         if (doc.dockingConfig) {
@@ -588,6 +577,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return (null);
         }
         let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
+        console.log("pw = " + this.panelWidth() + "," + this.panelHeight() + " " + this.contentScaling());
         return <DocumentView key={this._document[Id]}
             Document={this._document}
             DataDoc={resolvedDataDoc}
@@ -595,8 +585,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             addDocument={undefined}
             removeDocument={undefined}
             ContentScaling={this.contentScaling}
-            PanelWidth={this.nativeWidth}
-            PanelHeight={this.nativeHeight}
+            PanelWidth={this.panelWidth}
+            PanelHeight={this.panelHeight}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
             selectOnLoad={false}
@@ -610,18 +600,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     @computed get content() {
-        if (!this._document) {
-            return (null);
-        }
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
-                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
+                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
                 {this.docView}
             </div >);
     }
 
     render() {
-        if (!this._isActive) return null;
+        if (!this._isActive || !this._document) return null;
         let theContent = this.content;
         return !this._document ? (null) :
             <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 119aa7c19..8ce3b40e5 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -563,7 +563,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         height: "100%"
                     }}>
                     <DocumentView
-                        DataDoc={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDocument}
+                        DataDoc={this.props.DataDocument}
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 5a123bf65..3da13bff5 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -46,14 +46,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return fields;
     }
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
+        this._heightDisposer = reaction(() => [this.yMargin, this.props.Document[WidthSym](), this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
             () => {
-                if (this.singleColumn) {
+                if (this.singleColumn && BoolCast(this.props.Document.autoHeight)) {
                     let hgt = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) => {
                         let xhgt = height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap);
                         return xhgt;
                     }, this.yMargin);
-                    this.layoutDoc.height = hgt;
+                    this.layoutDoc.height = hgt * (this.props as any).ContentScaling();
                 }
             }, { fireImmediately: true });
     }
@@ -74,14 +74,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: "title", caption: "caption" } : {};
     }
 
-    getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
-        let resolvedDataDoc = !this.layoutDoc.isTemplate && this.props.DataDoc !== this.layoutDoc ? this.props.DataDoc : undefined;
-        let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
+    getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
         let height = () => this.getDocHeight(layoutDoc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
         return <CollectionSchemaPreview
             Document={layoutDoc}
-            DataDocument={resolvedDataDoc}
+            DataDocument={dataDoc}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth}
             fitToBox={true}
@@ -102,51 +100,37 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDocHeight(d: Doc) {
         let nw = NumCast(d.nativeWidth);
         let nh = NumCast(d.nativeHeight);
-        let aspect = nw && nh ? nh / nw : 1;
-        let wid = Math.min(d[WidthSym](), this.columnWidth);
-        return (nw && nh) ? wid * aspect : d[HeightSym]();
+        if (!BoolCast(d.ignoreAspect) && nw && nh) {
+            let aspect = nw && nh ? nh / nw : 1;
+            let wid = Math.min(d[WidthSym](), this.columnWidth);
+            return wid * aspect;
+        }
+        return d[HeightSym]();
     }
 
-    offsetTransform(doc: Doc, translateX: number, translateY: number) {
+    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.columnWidth);
     }
-    getDocTransform(doc: Doc, dref: HTMLDivElement) {
-        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
-        return this.offsetTransform(doc, translateX, translateY);
-    }
-
-    getSingleDocTransform(doc: Doc, ind: number, width: number) {
-        let localY = this.filteredChildren.reduce((height, d, i) =>
-            height + (i < ind ? this.getDocHeight(Doc.expandTemplateLayout(d, this.props.DataDoc)) + this.gridGap : 0), this.yMargin);
-        let translate = this.props.ScreenToLocalTransform().inverse().transformPoint((this.props.PanelWidth() - width) / 2, localY);
-        return this.offsetTransform(doc, translate[0], translate[1]);
-    }
 
     children(docs: Doc[]) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
-            let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
-            let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
-            let height = () => this.getDocHeight(layoutDoc);
-            if (this.singleColumn) {
-                //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
-                let dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
-                let rowHgtPcnt = height();
-                this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-columnDoc" key={d[Id]} style={{ width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` }} >
-                    {this.getDisplayDoc(layoutDoc, d, dxf)}
-                </div>;
-            } else {
-                let dref = React.createRef<HTMLDivElement>();
-                let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
-                let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
-                this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
-                    {this.getDisplayDoc(layoutDoc, d, dxf)}
-                </div>;
-            }
+            let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d)
+            let width = () => d.nativeWidth && !BoolCast(d.ignoreAspect) ? Math.min(pair.layout[WidthSym](), this.columnWidth) : this.columnWidth;
+            let height = () => this.getDocHeight(pair.layout);
+            let dref = React.createRef<HTMLDivElement>();
+            let dxf = () => this.getDocTransform(pair.layout, dref.current!);
+            this._docXfs.push({ dxf: dxf, width: width, height: height });
+            let rowHgtPcnt = height();
+            let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
+            let style = this.singleColumn ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` };
+
+            return <div className={`collectionStackingView-${this.singleColumn ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
+                {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
+            </div>;
         });
     }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7781b26d9..cd137cd60 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -15,8 +15,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
-import { StrCast, PromiseValue } from '../../../new_fields/Types';
-import { DocumentType } from '../../documents/Documents';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d61aefc7..f83d02271 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -61,11 +61,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private inkKey = "ink";
 
     get parentScaling() {
-        return (this.props as any).ContentScaling && this.Document.nativeWidth && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
+        return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
     }
 
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.nativeWidth && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
+        let bounds = this.fitToBox && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
         return {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -370,24 +370,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
         let self = this;
-        let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
-        let layoutDoc = childDocLayout;
-        if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
-            Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
-            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
-            layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
-        } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+        let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
         return {
-            DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
-            Document: layoutDoc,
+            DataDoc: pair.data,
+            Document: pair.layout,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
-            PanelWidth: layoutDoc[WidthSym],
-            PanelHeight: layoutDoc[HeightSym],
+            selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
+            PanelWidth: pair.layout[WidthSym],
+            PanelHeight: pair.layout[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 389b9f68b..7ffd760e0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -35,21 +35,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
     @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
     @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
-
-    set width(w: number) {
-        this.Document.width = w;
-        if (this.nativeWidth && this.nativeHeight) {
-            this.Document.height = this.nativeHeight / this.nativeWidth * w;
-        }
-    }
-    set height(h: number) {
-        this.Document.height = h;
-        if (this.nativeWidth && this.nativeHeight) {
-            this.Document.width = this.nativeWidth / this.nativeHeight * h;
-        }
-    }
     @computed get scaleToOverridingWidth() { return this.width / NumCast(this.props.Document.width, this.width); }
-    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
+
+    contentScaling = () => this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bae0b5b96..3152d6a2a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -507,12 +507,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     freezeNativeDimensions = (): void => {
         let proto = this.props.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document);
-        if (proto.ignoreAspect === undefined && !proto.nativeWidth) {
+        this.props.Document.autoHeight = proto.autoHeight = false;
+        proto.ignoreAspect = !BoolCast(proto.ignoreAspect);
+        if (!BoolCast(proto.ignoreAspect) && !proto.nativeWidth) {
             proto.nativeWidth = this.props.PanelWidth();
             proto.nativeHeight = this.props.PanelHeight();
-            proto.ignoreAspect = true;
         }
-        proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false);
     }
     @undoBatch
     @action
@@ -546,7 +546,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" });
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
-        cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
+        cm.addItem({ description: !BoolCast(this.props.Document.ignoreAspect) && this.props.Document.nativeWidth && this.props.Document.nativeHeight ? "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: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         cm.addItem({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
@@ -642,7 +642,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let self = this;
         let backgroundColor = StrCast(this.layoutDoc.backgroundColor);
         let foregroundColor = StrCast(this.layoutDoc.color);
-        var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
+        var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined;
         let showTitle = showOverlays && showOverlays.title !== "undefined" ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
@@ -665,6 +665,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     outlineStyle: "dashed",
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
+                    marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+                    marginTop: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1a00db1c1..dbb0dc505 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -389,11 +389,22 @@ export namespace Doc {
         }
         if (expandedTemplateLayout === undefined) {
             setTimeout(() =>
-                dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "["+templateLayoutDoc.title + "]"), 0);
+                dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"), 0);
         }
         return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
     }
 
+    export function GetLayoutDataDocPair(doc: Doc, dataDoc: Doc | undefined, fieldKey: string, childDocLayout: Doc) {
+        let layoutDoc = childDocLayout;
+        let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc ? dataDoc : undefined;
+        if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
+            Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey);
+            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+            layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
+        } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+        return { layout: layoutDoc, data: resolvedDataDoc };
+    }
+
     export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
         const copy = new Doc;
         Object.keys(doc).forEach(key => {
-- 
cgit v1.2.3-70-g09d2


From 103bffdc997bd41563c282db52ce7132c98b8f28 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 27 Jul 2019 10:11:16 -0400
Subject: added additional groupings and fixed search selection styling

---
 src/client/views/ContextMenu.scss                           |  8 +++++---
 src/client/views/ContextMenu.tsx                            |  4 ++++
 src/client/views/ContextMenuItem.tsx                        |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx           | 13 ++++++++-----
 src/client/views/nodes/DocumentView.tsx                     | 12 +++++++-----
 5 files changed, 25 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 195304b2c..e39b1034a 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -31,6 +31,7 @@
     z-index: 1000;
     box-shadow: #AAAAAA .2vw .2vw .4vw;
     flex-direction: column;
+    border: 1px solid #BBBBBBBB;
 }
 
 .contextMenu-item {
@@ -54,7 +55,7 @@
     color: grey;
     letter-spacing: 2px;
     text-transform: uppercase;
-    padding-right: 10px;
+    padding-right: 30px;
 }
 
 .contextMenu-item:hover {
@@ -66,13 +67,14 @@
 }
 
 .contextMenu-itemSelected {
-    background: rgb(136, 136, 136)
+    background: lightgoldenrodyellow;
+    border-style: none;
 }
 
 .contextMenu-group {
     // width: 11vw; //10vw
     height: 30px; //2vh
-    background: lightgoldenrodyellow;
+    background: lightgray;
     display: flex; //comment out to allow search icon to be inline with search text
     justify-content: left;
     align-items: center;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 5caf27327..a608e448a 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -38,6 +38,10 @@ export class ContextMenu extends React.Component {
         this._items = [];
     }
 
+    findByDescription = (target: string) => {
+        return this._items.find(menuItem => menuItem.description === target);
+    }
+
     @action
     addItem(item: ContextMenuProps) {
         if (this._items.indexOf(item) === -1) {
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 4a7cd309d..097cb2c12 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -94,7 +94,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                     ) : null}
                     <div className="contextMenu-description">
                         {this.props.description}
-                        <FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px" }} />
+                        <FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px", marginTop: "-2px"}} />
                     </div>
                     {submenu}
                 </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d61aefc7..790c312fa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -13,7 +13,7 @@ import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
-import { ContextMenu } from "../../ContextMenu";
+import { SubmenuProps, ContextMenuProps } from "../../ContextMenuItem";
 import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
@@ -34,11 +34,12 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt } from "@fortawesome/free-solid-svg-icons";
+import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass } from "@fortawesome/free-solid-svg-icons";
 import { undo } from "prosemirror-history";
 import { number } from "prop-types";
+import { ContextMenu } from "../../ContextMenu";
 
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -498,12 +499,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     onContextMenu = () => {
-        ContextMenu.Instance.addItem({
+        let layoutItems: ContextMenuProps[] = [];
+        layoutItems.push({
             description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`,
             event: undoBatch(async () => this.props.Document.fitToBox = !this.fitToBox),
             icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
         });
-        ContextMenu.Instance.addItem({
+        layoutItems.push({
             description: "Arrange contents in grid",
             icon: "table",
             event: async () => {
@@ -530,6 +532,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }, "arrange contents");
             }
         });
+        ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
         ContextMenu.Instance.addItem({
             description: "Analyze Strokes", event: async () => {
                 let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index da72f212c..392faf3e9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -61,8 +61,7 @@ library.add(fa.faCrosshairs);
 library.add(fa.faDesktop);
 library.add(fa.faUnlock);
 library.add(fa.faLock);
-library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, faHandPointRight);
-
+library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake);
 
 // const linkSchema = createSchema({
 //     title: "string",
@@ -547,7 +546,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" });
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
-        cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
+        cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
         cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         let makes: ContextMenuProps[] = [];
@@ -575,8 +574,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
         }
         cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
-        cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
-        cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
+        let existing = ContextMenu.Instance.findByDescription("Layout...");
+        let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
+        layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
+        layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
+        !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
         if (!ClientUtils.RELEASE) {
             let copies: ContextMenuProps[] = [];
             copies.push({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
-- 
cgit v1.2.3-70-g09d2


From c360dc0adb468ae3aaa1c2d943606993d01a5a52 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 27 Jul 2019 11:08:44 -0400
Subject: fixed handling of keyboard events to avoid triggering global key
 events. made BoolCast default to false. fixed templating to deal with
 conflict between field fields and template fields

---
 src/client/util/DocumentManager.ts                 | 16 +++++++--------
 src/client/views/EditableView.tsx                  |  3 +++
 src/client/views/MainView.tsx                      |  1 -
 src/client/views/MetadataEntryMenu.tsx             |  1 +
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  3 ++-
 .../CollectionFreeFormLinkView.tsx                 |  8 ++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  8 ++++----
 src/client/views/nodes/FormattedTextBox.tsx        |  4 ++--
 src/client/views/nodes/IconBox.tsx                 |  2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  3 ++-
 src/client/views/nodes/LinkMenuItem.tsx            |  2 +-
 src/client/views/pdf/Page.tsx                      | 22 ++++++++------------
 .../views/presentationview/PresentationElement.tsx | 24 ++++++++++------------
 .../views/presentationview/PresentationView.tsx    |  2 +-
 src/new_fields/Doc.ts                              | 19 +++++++++--------
 src/new_fields/Types.ts                            |  2 +-
 18 files changed, 62 insertions(+), 62 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 262194a40..32f728c71 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,16 +1,14 @@
-import { computed, observable, action } from 'mobx';
-import { DocumentView } from '../views/nodes/DocumentView';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
-import { FieldValue, Cast, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
-import { listSpec } from '../../new_fields/Schema';
-import { undoBatch, UndoManager } from './UndoManager';
+import { action, computed, observable } from 'mobx';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/FieldSymbols';
+import { BoolCast, Cast, NumCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionView } from '../views/collections/CollectionView';
 import { CollectionPDFView } from '../views/collections/CollectionPDFView';
 import { CollectionVideoView } from '../views/collections/CollectionVideoView';
-import { Id } from '../../new_fields/FieldSymbols';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DocumentView } from '../views/nodes/DocumentView';
 import { LinkManager } from './LinkManager';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { undoBatch, UndoManager } from './UndoManager';
 
 
 export class DocumentManager {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c66a92f48..7cabebddd 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -50,8 +50,10 @@ export class EditableView extends React.Component<EditableProps> {
     @action
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Tab") {
+            e.stopPropagation();
             this.props.OnTab && this.props.OnTab();
         } else if (e.key === "Enter") {
+            e.stopPropagation();
             if (!e.ctrlKey) {
                 if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
                     this._editing = false;
@@ -61,6 +63,7 @@ export class EditableView extends React.Component<EditableProps> {
                 this._editing = false;
             }
         } else if (e.key === "Escape") {
+            e.stopPropagation();
             this._editing = false;
         }
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bfb50bc75..8b7eb8856 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -7,7 +7,6 @@ import "normalize.css";
 import * as React from 'react';
 import { SketchPicker } from 'react-color';
 import Measure from 'react-measure';
-import * as request from 'request';
 import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index bd5a307b3..c50e02f0d 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -76,6 +76,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
 
     onValueKeyDown = async (e: React.KeyboardEvent) => {
         if (e.key === "Enter") {
+            e.stopPropagation();
             const script = KeyValueBox.CompileKVPScript(this._currentValue);
             if (!script) return;
             let doc = this.props.docs;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 8ce3b40e5..0f67c47aa 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,7 +11,7 @@ import { Doc, DocListCast, DocListCastAsync, Field } from "../../../new_fields/D
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs } from "../../documents/Documents";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { SetupDrag, DragManager } from "../../util/DragManager";
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 3da13bff5..d5a7b90da 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -53,7 +53,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         let xhgt = height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap);
                         return xhgt;
                     }, this.yMargin);
-                    this.layoutDoc.height = hgt * (this.props as any).ContentScaling();
+                    (this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc)
+                        .height = hgt * (this.props as any).ContentScaling();
                 }
             }, { fireImmediately: true });
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index b546d1b78..6af87b138 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -21,10 +21,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (e.button === 0 && !InkingControl.Instance.selectedTool) {
             let a = this.props.A;
             let b = this.props.B;
-            let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : a[WidthSym]() / 2);
-            let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
-            let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
-            let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
+            let x1 = NumCast(a.x) + (BoolCast(a.isMinimized) ? 5 : a[WidthSym]() / 2);
+            let y1 = NumCast(a.y) + (BoolCast(a.isMinimized) ? 5 : a[HeightSym]() / 2);
+            let x2 = NumCast(b.x) + (BoolCast(b.isMinimized) ? 5 : b[WidthSym]() / 2);
+            let y2 = NumCast(b.y) + (BoolCast(b.isMinimized) ? 5 : b[HeightSym]() / 2);
             // this.props.LinkDocs.map(l => {
             //     let width = l[WidthSym]();
             //     l.x = (x1 + x2) / 2 - width / 2;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f83d02271..eaad43c10 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -471,7 +471,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (!(doc instanceof Doc)) return prev;
             var page = NumCast(doc.page, -1);
             if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
-                let minim = BoolCast(doc.isMinimized, false);
+                let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
                     state = pos.state === undefined ? state : pos.state;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3152d6a2a..bc2af2ae8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -275,7 +275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
                     if (isMinimized === undefined) {
-                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                        isMinimized = BoolCast(maximizedDoc.isMinimized);
                     }
                     maximizedDoc.willMaximize = isMinimized;
                     maximizedDoc.isMinimized = false;
@@ -308,7 +308,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 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) {
+            if (BoolCast(this.props.Document.isButton) || isExpander) {
                 SelectionManager.DeselectAll();
                 let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
                 let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
@@ -326,7 +326,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         maxLocation = this.props.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace"));
                         if (!maxLocation || maxLocation === "inPlace") {
                             let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
-                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
+                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized), false);
                             expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
                             let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
                             if (!hasView) {
@@ -409,7 +409,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
-        doc.isButton = !BoolCast(doc.isButton, false);
+        doc.isButton = !BoolCast(doc.isButton);
         if (doc.isButton) {
             if (!doc.nativeWidth) {
                 doc.nativeWidth = this.props.Document[WidthSym]();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0a79677e2..bd0fd8301 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -459,8 +459,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     specificContextMenu = (e: React.MouseEvent): void => {
         let subitems: ContextMenuProps[] = [];
         subitems.push({
-            description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height",
-            event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt"
+            description: BoolCast(this.props.Document.autoHeight) ? "Manual Height" : "Auto Height",
+            event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems });
     }
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index d6ab2a34a..6f50033a4 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -64,7 +64,7 @@ export class IconBox extends React.Component<FieldViewProps> {
         let hideLabel = BoolCast(this.props.Document.hideLabel);
         let maxDocs = DocListCast(this.props.Document.maximizedDocs);
         let firstDoc = maxDocs.length ? maxDocs[0] : undefined;
-        let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title);
+        let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle) ? firstDoc[labelField] : this.props.Document.title);
         return (
             <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
                 {this.minimizedIcon}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9fc0f2080..77824b4ff 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -46,6 +46,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key === 'Enter') {
+            e.stopPropagation();
             if (this._keyInput && this._valueInput && this.fieldDocToLayout) {
                 if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput, this._valueInput)) {
                     this._keyInput = "";
@@ -153,7 +154,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                     <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} />
                 </td>
                 <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}>
-                    <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} />
+                    <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyDown={this.onEnterKey} />
                 </td>
             </tr>
         )
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index a0c37a719..d4c92c9f2 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -7,7 +7,7 @@ import { undoBatch } from "../../util/UndoManager";
 import './LinkMenu.scss';
 import React = require("react");
 import { Doc } from '../../../new_fields/Doc';
-import { StrCast, Cast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types';
+import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { observable, action } from 'mobx';
 import { LinkManager } from '../../util/LinkManager';
 import { DragLinkAsDocument } from '../../util/DragManager';
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index c9d442fe5..a6864e0f3 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -1,22 +1,18 @@
+import { action, IReactionDisposer, observable } from "mobx";
 import { observer } from "mobx-react";
-import React = require("react");
-import { observable, action, runInAction, IReactionDisposer, reaction } from "mobx";
 import * as Pdfjs from "pdfjs-dist";
-import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym, DocListCastAsync } from "../../../new_fields/Doc";
-import "./PDFViewer.scss";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { PDFBox } from "../nodes/PDFBox";
-import { DragManager } from "../../util/DragManager";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
-import { emptyFunction } from "../../../Utils";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
-import { menuBar } from "prosemirror-menu";
-import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { DragManager } from "../../util/DragManager";
+import { PDFBox } from "../nodes/PDFBox";
 import PDFMenu from "./PDFMenu";
-import { UndoManager } from "../../util/UndoManager";
-import { copy } from "typescript-collections/dist/lib/arrays";
+import { scale } from "./PDFViewer";
+import "./PDFViewer.scss";
+import React = require("react");
 
 
 interface IPageProps {
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 329630875..36f1178f1 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -1,21 +1,19 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { NumCast, BoolCast, StrCast, Cast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { observable, action, computed, runInAction } from "mobx";
-import "./PresentationView.scss";
-import { Utils } from "../../../Utils";
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons';
 import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
+import { faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
+import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
-import { indexOf } from "typescript-collections/dist/lib/arrays";
-import { map } from "bluebird";
+import "./PresentationView.scss";
+import React = require("react");
 
 library.add(faArrowUp);
 library.add(fileSolid);
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f2fef7f16..e25725275 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -171,7 +171,7 @@ export class PresentationView extends React.Component<PresViewProps>  {
 
 
         //storing the presentation status,ie. whether it was stopped or playing
-        let presStatusBackUp = BoolCast(this.curPresentation.presStatus, null);
+        let presStatusBackUp = BoolCast(this.curPresentation.presStatus);
         runInAction(() => this.presStatus = presStatusBackUp);
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index dbb0dc505..b5708e97b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -455,7 +455,7 @@ export namespace Doc {
         return otherdoc;
     }
 
-    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, proto: Doc) {
+    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
@@ -466,21 +466,24 @@ export namespace Doc {
         if (backgroundLayout) {
             backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
         }
-        let nw = Cast(fieldTemplate.nativeWidth, "number");
-        let nh = Cast(fieldTemplate.nativeHeight, "number");
 
         let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
         layoutDelegate.layout = layout;
 
-        fieldTemplate.title = metaKey;
         fieldTemplate.templateField = metaKey;
+        fieldTemplate.title = metaKey;
+        fieldTemplate.isTemplate = true;
         fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
         fieldTemplate.backgroundLayout = backgroundLayout;
-        fieldTemplate.nativeWidth = nw;
-        fieldTemplate.nativeHeight = nh;
-        fieldTemplate.isTemplate = true;
+        /* move certain layout properties from the original data doc to the template layout to avoid
+           inheriting them from the template's data doc which may also define these fields for its own use.
+        */
+        fieldTemplate.ignoreAspect = BoolCast(fieldTemplate.ignoreAspect);
+        fieldTemplate.singleColumn = BoolCast(fieldTemplate.singleColumn);
+        fieldTemplate.nativeWidth = Cast(fieldTemplate.nativeWidth, "number");
+        fieldTemplate.nativeHeight = Cast(fieldTemplate.nativeHeight, "number");
         fieldTemplate.showTitle = "title";
-        setTimeout(() => fieldTemplate.proto = proto);
+        setTimeout(() => fieldTemplate.proto = templateDataDoc);
     }
 
     export async function ToggleDetailLayout(d: Doc) {
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index f8a4a30b4..565ae2ee3 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -78,7 +78,7 @@ export function StrCast(field: FieldResult, defaultVal: string | null = "") {
     return Cast(field, "string", defaultVal);
 }
 
-export function BoolCast(field: FieldResult, defaultVal: boolean | null = null) {
+export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) {
     return Cast(field, "boolean", defaultVal);
 }
 export function DateCast(field: FieldResult) {
-- 
cgit v1.2.3-70-g09d2


From 60aa214501f7932e5efee68fd878ef367fc2a31b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 27 Jul 2019 16:18:24 -0400
Subject: switch Make Portal to act more like a prezi portal.  added scale
 paramter to focus()

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx            | 22 +++++++++++++++-------
 2 files changed, 20 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eaad43c10..cd071fb4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -303,7 +303,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         doc.zIndex = docs.length + 1;
     }
 
-    focusDocument = (doc: Doc, willZoom: boolean) => {
+    focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => {
         const panX = this.Document.panX;
         const panY = this.Document.panY;
         const id = this.Document[Id];
@@ -335,20 +335,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.props.Document.panTransformType = "Ease";
         this.props.focus(this.props.Document);
         if (willZoom) {
-            this.setScaleToZoom(doc);
+            this.setScaleToZoom(doc, scale);
         }
 
     }
 
-    setScaleToZoom = (doc: Doc) => {
+    setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
         let p = this.props;
         let PanelHeight = p.PanelHeight();
         let panelWidth = p.PanelWidth();
 
         let docHeight = NumCast(doc.height);
         let docWidth = NumCast(doc.width);
-        let targetHeight = 0.5 * PanelHeight;
-        let targetWidth = 0.5 * panelWidth;
+        let targetHeight = scale * PanelHeight;
+        let targetWidth = scale * panelWidth;
 
         let maxScaleX: number = targetWidth / docWidth;
         let maxScaleY: number = targetHeight / docHeight;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bc2af2ae8..f60be0a6a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,7 +88,7 @@ export interface DocumentViewProps {
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    focus: (doc: Doc, willZoom: boolean) => void;
+    focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -359,7 +359,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     if (!linkedFwdDocs.some(l => l instanceof Promise)) {
                         let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
                         let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => this.props.addDocTab(document, undefined, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => {
+                            this.props.focus(this.props.Document, true, 1);
+                            setTimeout(() =>
+                                this.props.addDocTab(document, undefined, maxLocation), 1000);
+                        }
+                            , linkedFwdPage[altKey ? 1 : 0], targetContext);
                     }
                 }
             }
@@ -554,12 +559,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({
             description: "Make Portal", event: () => {
                 let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" });
-                Doc.GetProto(this.props.Document).subBulletDocs = new List<Doc>([portal]);
+                //Doc.GetProto(this.props.Document).subBulletDocs = new List<Doc>([portal]);
                 //summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-                Doc.GetProto(this.props.Document).templates = new List<string>([Templates.Bullet.Layout]);
-                let coll = Docs.Create.StackingDocument([this.props.Document, portal], { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".cont" });
-                this.props.addDocument && this.props.addDocument(coll);
-                this.props.removeDocument && this.props.removeDocument(this.props.Document);
+                //Doc.GetProto(this.props.Document).templates = new List<string>([Templates.Bullet.Layout]);
+                //let coll = Docs.Create.StackingDocument([this.props.Document, portal], { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".cont" });
+                //this.props.addDocument && this.props.addDocument(coll);
+                //this.props.removeDocument && this.props.removeDocument(this.props.Document);
+                DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal");
+                this.makeBtnClicked();
+
             }, icon: "window-restore"
         });
         // cm.addItem({
-- 
cgit v1.2.3-70-g09d2


From 27cafb6eebd1c6229c3377187a5c0043db25ba0a Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 28 Jul 2019 22:58:32 -0400
Subject: templating fixes, context menu undo streamlining

---
 src/.DS_Store                                         | Bin 6148 -> 6148 bytes
 src/client/documents/Documents.ts                     |   5 ++---
 src/client/views/ContextMenuItem.tsx                  |   4 ++--
 src/client/views/collections/CollectionTreeView.tsx   |  10 +++++-----
 src/client/views/collections/CollectionView.tsx       |  14 +++++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx     |   2 +-
 src/client/views/nodes/KeyValueBox.tsx                |  17 ++++++++++++-----
 src/scraping/buxton/scraper.py                        |   4 ++--
 8 files changed, 31 insertions(+), 25 deletions(-)

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

diff --git a/src/.DS_Store b/src/.DS_Store
index 071dafa1e..c544bc837 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 3859f2255..30a114e33 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -163,7 +163,6 @@ export namespace Docs {
             [DocumentType.LINKDOC, {
                 data: new List<Doc>(),
                 layout: { view: EmptyBox },
-                options: {}
             }],
             [DocumentType.BUTTON, {
                 layout: { view: ButtonBox },
@@ -414,8 +413,8 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
         }
 
-        export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking });
+        export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, viewType: CollectionViewType = CollectionViewType.Stacking) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: viewType });
         }
 
         export function ButtonDocument(options?: DocumentOptions) {
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 4ff0fe388..a1787e78f 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -37,13 +37,13 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
         }
     }
 
-    handleEvent = (e: React.MouseEvent<HTMLDivElement>) => {
+    handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
         if ("event" in this.props) {
             let batch: UndoManager.Batch | undefined;
             if (this.props.undoable !== false) {
                 batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`);
             }
-            this.props.event();
+            await this.props.event();
             batch && batch.end();
             this.props.closeMenu && this.props.closeMenu();
         }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1c7fe4bee..3eab109bc 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -237,10 +237,10 @@ class TreeView extends React.Component<TreeViewProps> {
                 if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
                     ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.dataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" });
                 }
-                ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
+                ContextMenu.Instance.addItem({ description: "Delete Item", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
             } else {
-                ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.dataDoc)), icon: "caret-square-right" });
-                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
+                ContextMenu.Instance.addItem({ description: "Open as Workspace", event: () => MainView.Instance.openWorkspace(this.dataDoc), icon: "caret-square-right" });
+                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
             }
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
             ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
@@ -520,8 +520,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document
-            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()), icon: "plus" });
-            ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)), icon: "minus" });
+            ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
+            ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" });
             e.stopPropagation();
             e.preventDefault();
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b4f29755c..e3f5d1eb8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -53,14 +53,14 @@ export class CollectionView extends React.Component<FieldViewProps> {
     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
             let subItems: ContextMenuProps[] = [];
-            subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
+            subItems.push({ description: "Freeform", event: () => 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: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
             }
-            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: "ellipsis-v" });
-            subItems.push({ description: "Masonry", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Masonry), icon: "columns" });
+            subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" });
+            subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" });
+            subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
+            subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
             switch (this.props.Document.viewType) {
                 case CollectionViewType.Freeform: {
                     subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
@@ -68,7 +68,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
                 }
             }
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
-            ContextMenu.Instance.addItem({ description: "Apply Template", event: undoBatch(() => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight")), icon: "project-diagram" });
+            ContextMenu.Instance.addItem({ description: "Apply Template", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d07fc7f80..a6152e1b7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -496,7 +496,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let layoutItems: ContextMenuProps[] = [];
         layoutItems.push({
             description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`,
-            event: undoBatch(async () => this.props.Document.fitToBox = !this.fitToBox),
+            event: async () => this.props.Document.fitToBox = !this.fitToBox,
             icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
         });
         layoutItems.push({
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 77824b4ff..b0fcc4202 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -20,6 +20,7 @@ import { RichTextField } from "../../../new_fields/RichTextField";
 import { ImageField } from "../../../new_fields/URLField";
 import { SelectionManager } from "../../util/SelectionManager";
 import { listSpec } from "../../../new_fields/Schema";
+import { CollectionViewType } from "../collections/CollectionBaseView";
 
 export type KVPScript = {
     script: CompiledScript;
@@ -195,6 +196,9 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         }
 
         let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
+        if (!fieldTemplate) {
+            return;
+        }
         let previousViewType = fieldTemplate.viewType;
         Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc));
         previousViewType && (fieldTemplate.viewType = previousViewType);
@@ -211,14 +215,17 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 return Docs.Create.StackingDocument([], options);
             }
             let first = await Cast(data[0], Doc);
-            if (!first) {
+            if (!first || !first.data) {
                 return Docs.Create.StackingDocument([], options);
             }
-            switch (first.type) {
-                case "image":
-                    return Docs.Create.StackingDocument([], options);
-                case "text":
+            switch (first.data.constructor) {
+                case RichTextField:
                     return Docs.Create.TreeDocument([], options);
+                case ImageField:
+                    return Docs.Create.StackingDocument([], options, CollectionViewType.Masonry);
+                default:
+                    console.log(`Template for ${first.data.constructor} not supported!`);
+                    return undefined;
             }
         } else if (data instanceof ImageField) {
             return Docs.Create.ImageDocument("https://image.flaticon.com/icons/png/512/23/23765.png", options);
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 182b22a1a..1ff0e3b31 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -139,7 +139,7 @@ def write_text_doc(content):
     data_doc = {
         "_id": data_doc_guid,
         "fields": {
-            "proto": protofy("commonImportProto"),
+            "proto": protofy("textProto"),
             "data": {
                 "Data": '{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + content + '"}]}]},"selection":{"type":"text","anchor":1,"head":1}' + '}',
                 "__type": "RichTextField"
@@ -235,7 +235,7 @@ def parse_document(file_name: str):
         count += 1
         view_guids.append(write_image(pure_name, image))
         copyfile(dir_path + "/" + image, dir_path +
-                  "/" + image.replace(".", "_o.", 1))
+                 "/" + image.replace(".", "_o.", 1))
         os.rename(dir_path + "/" + image, dir_path +
                   "/" + image.replace(".", "_m.", 1))
     print(f"extracted {count} images...")
-- 
cgit v1.2.3-70-g09d2


From 2514917040d24c04a489905c7a1fe4d10013fd31 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 29 Jul 2019 20:50:04 -0400
Subject: not elegant, but should compute bounds for computed layouts

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 36 +++++++++++++++++-----
 1 file changed, 28 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0780320d6..d2c95532e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -65,8 +65,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
     }
 
+    ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
+        let bounds = boundsList.reduce((bounds, b) => {
+            var [sptX, sptY] = [b.x, b.y];
+            let [bptX, bptY] = [sptX + b.width, sptY + b.height];
+            return {
+                x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+                r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+            };
+        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+        return bounds;
+    }
+
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
+        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined;
         return {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -446,13 +458,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed.struct
-    get views() {
+    get elements() {
         let curPage = FieldValue(this.Document.curPage, -1);
         const initScript = this.Document.arrangeInit;
         const script = this.Document.arrangeScript;
         let state: any = undefined;
         const docs = this.childDocs;
-        let elements: JSX.Element[] = [];
+        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
@@ -460,23 +472,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const { state: scriptState, views } = result;
                 state = scriptState;
                 if (Array.isArray(views)) {
-                    elements = views.reduce<JSX.Element[]>((prev, ele) => {
+                    elements = views.reduce((prev, ele) => {
                         const jsx = this.viewDefToJSX(ele);
-                        jsx && prev.push(jsx);
+                        jsx && prev.push({ ele: jsx });
                         return prev;
                     }, elements);
                 }
             }
         }
-        let docviews = docs.reduce((prev, doc) => {
-            if (!(doc instanceof Doc)) return prev;
+        let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => {
             var page = NumCast(doc.page, -1);
+            let bounds: { x?: number, y?: number, width?: number, height?: number };
             if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
                 let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
                     state = pos.state === undefined ? state : pos.state;
-                    prev.push(<CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />);
+                    prev.push({
+                        ele: <CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />,
+                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined
+                    });
                 }
             }
             return prev;
@@ -487,6 +502,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return docviews;
     }
 
+    @computed.struct
+    get views() {
+        return this.elements.map(ele => ele.ele);
+    }
+
     @action
     onCursorMove = (e: React.PointerEvent) => {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
-- 
cgit v1.2.3-70-g09d2


From e042f916375fbe4f23288ece0dcec5b61ef2fbed Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Mon, 29 Jul 2019 21:20:51 -0400
Subject: annotations now open the target context

---
 src/client/util/DragManager.ts                               |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  2 ++
 src/client/views/nodes/DocumentView.tsx                      |  4 +++-
 src/client/views/pdf/Annotation.tsx                          | 12 +++++++++---
 src/client/views/pdf/Page.tsx                                |  2 +-
 5 files changed, 16 insertions(+), 5 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9221ef274..abcc3a4e1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -216,6 +216,7 @@ export namespace DragManager {
             this.annotationDocument = annotationDoc;
             this.xOffset = this.yOffset = 0;
         }
+        targetContext: Doc | undefined;
         dragDocument: Doc;
         annotationDocument: Doc;
         dropDocument: Doc;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0780320d6..179a65739 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -151,6 +151,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     let dropY = NumCast(de.data.dropDocument.y);
                     dragDoc.x = x + NumCast(dragDoc.x) - dropX;
                     dragDoc.y = y + NumCast(dragDoc.y) - dropY;
+                    de.data.targetContext = this.props.Document;
+                    dragDoc.targetContext = this.props.Document;
                     this.bringToFront(dragDoc);
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f101222ae..b37485e62 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -439,7 +439,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             let annotationDoc = de.data.annotationDocument;
             annotationDoc.linkedToDoc = true;
+            de.data.targetContext = this.props.ContainingCollectionView!.props.Document;
             let targetDoc = this.props.Document;
+            targetDoc.targetContext = de.data.targetContext;
             let annotations = await DocListCastAsync(annotationDoc.annotations);
             if (annotations) {
                 annotations.forEach(anno => {
@@ -448,7 +450,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
             if (pdfDoc) {
-                DocUtils.MakeLink(annotationDoc, targetDoc, undefined, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
+                DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
             }
         }
         if (de.data instanceof DragManager.LinkDragData) {
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index ed7081b1d..e8d1da94b 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -9,6 +9,8 @@ import { List } from "../../../new_fields/List";
 import PDFMenu from "./PDFMenu";
 import { DocumentManager } from "../../util/DocumentManager";
 import { PresentationView } from "../presentationview/PresentationView";
+import { LinkManager } from "../../util/LinkManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -110,11 +112,15 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     @action
-    onPointerDown = (e: React.PointerEvent) => {
+    onPointerDown = async (e: React.PointerEvent) => {
         if (e.button === 0) {
-            let targetDoc = Cast(this.props.document.target, Doc, null);
+            let targetDoc = await Cast(this.props.document.target, Doc);
             if (targetDoc) {
-                DocumentManager.Instance.jumpToDocument(targetDoc, false);
+                let context = await Cast(targetDoc.targetContext, Doc);
+                if (context) {
+                    DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined,
+                        ((doc) => this.props.parent.props.parent.props.addDocTab(context, context.proto, e.ctrlKey ? "onRight" : "inTab")));
+                }
             }
         }
         if (e.button === 2) {
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index c205617b4..c5b2a1dda 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -175,7 +175,7 @@ export default class Page extends React.Component<IPageProps> {
                             }
                             let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
                             if (pdfDoc) {
-                                DocUtils.MakeLink(annotationDoc, targetDoc, undefined, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
+                                DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
                             }
                         }
                     }
-- 
cgit v1.2.3-70-g09d2


From f6583e4b504a4a60f4aefc68c7a60692a92869dd Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 29 Jul 2019 22:33:35 -0400
Subject: Included extra ui elements in bounds calculation

---
 .../collectionFreeForm/CollectionFreeFormView.tsx      | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2b2f0bcd5..8dac785e1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -440,7 +440,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return result.result === undefined ? {} : result.result;
     }
 
-    private viewDefToJSX(viewDef: any): JSX.Element | undefined {
+    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined {
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string");
             const x = Cast(viewDef.x, "number");
@@ -448,14 +448,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const width = Cast(viewDef.width, "number");
             const height = Cast(viewDef.height, "number");
             const fontSize = Cast(viewDef.fontSize, "number");
-            if ([text, x, y].some(val => val === undefined)) {
+            if ([text, x, y, width, height].some(val => val === undefined)) {
                 return undefined;
             }
 
-            return <div className="collectionFreeform-customText" style={{
-                transform: `translate(${x}px, ${y}px)`,
-                width, height, fontSize
-            }}>{text}</div>;
+            return {
+                ele: <div className="collectionFreeform-customText" style={{
+                    transform: `translate(${x}px, ${y}px)`,
+                    width, height, fontSize
+                }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! }
+            };
         }
     }
 
@@ -474,9 +476,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const { state: scriptState, views } = result;
                 state = scriptState;
                 if (Array.isArray(views)) {
-                    elements = views.reduce((prev, ele) => {
+                    elements = views.reduce<typeof elements>((prev, ele) => {
                         const jsx = this.viewDefToJSX(ele);
-                        jsx && prev.push({ ele: jsx });
+                        jsx && prev.push(jsx);
                         return prev;
                     }, elements);
                 }
-- 
cgit v1.2.3-70-g09d2


From 26086ee95a9a16486d637aa43c96638b6154379f Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 30 Jul 2019 16:02:42 -0400
Subject: Added document export and import

---
 package.json                                       |   4 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  39 +++++-
 src/client/views/nodes/DocumentView.tsx            |  10 ++
 src/scraping/buxton/scraper.py                     |   2 +
 src/server/database.ts                             |  37 +++++-
 src/server/index.ts                                | 142 +++++++++++++++++----
 6 files changed, 202 insertions(+), 32 deletions(-)

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

diff --git a/package.json b/package.json
index 37052fde3..b29355738 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,9 @@
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.3.3",
         "@trendmicro/react-dropdown": "^1.3.0",
+        "@types/adm-zip": "^0.4.32",
         "@types/animejs": "^2.0.2",
+        "@types/archiver": "^3.0.0",
         "@types/async": "^2.4.1",
         "@types/bcrypt-nodejs": "0.0.30",
         "@types/bluebird": "^3.5.25",
@@ -105,6 +107,8 @@
         "@types/uuid": "^3.4.4",
         "@types/webpack": "^4.4.25",
         "@types/youtube": "0.0.38",
+        "adm-zip": "^0.4.13",
+        "archiver": "^3.0.3",
         "async": "^2.6.2",
         "babel-runtime": "^6.26.0",
         "bcrypt-nodejs": "0.0.3",
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8dac785e1..cbab14976 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne } from "../../../../Utils";
+import { emptyFunction, returnOne, Utils } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
@@ -34,12 +34,14 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass } from "@fortawesome/free-solid-svg-icons";
+import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload } from "@fortawesome/free-solid-svg-icons";
 import { undo } from "prosemirror-history";
 import { number } from "prop-types";
 import { ContextMenu } from "../../ContextMenu";
+import { RouteStore } from "../../../../server/RouteStore";
+import { DocServer } from "../../../DocServer";
 
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -516,7 +518,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
-    onContextMenu = () => {
+    onContextMenu = (e: React.MouseEvent) => {
         let layoutItems: ContextMenuProps[] = [];
         layoutItems.push({
             description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`,
@@ -561,6 +563,35 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData);
             }, icon: "paint-brush"
         });
+        ContextMenu.Instance.addItem({
+            description: "Import document", icon: "upload", event: () => {
+                const input = document.createElement("input");
+                input.type = "file";
+                input.accept = ".zip";
+                input.onchange = async _e => {
+                    const files = input.files;
+                    if (!files) return;
+                    const file = files[0];
+                    let formData = new FormData();
+                    formData.append('file', file);
+                    formData.append('remap', "true");
+                    const upload = Utils.prepend("/uploadDoc");
+                    const response = await fetch(upload, { method: "POST", body: formData });
+                    const json = await response.json();
+                    if (json === "error") {
+                        return;
+                    }
+                    const doc = await DocServer.GetRefField(json);
+                    if (!doc || !(doc instanceof Doc)) {
+                        return;
+                    }
+                    const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY);
+                    doc.x = x, doc.y = y;
+                    this.addDocument(doc, false);
+                };
+                input.click();
+            }
+        });
     }
 
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4b5cf3a43..58e2443c2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -45,6 +45,7 @@ const JsxParser = require('react-jsx-parser').default; //TODO Why does this need
 
 library.add(fa.faTrash);
 library.add(fa.faShare);
+library.add(fa.faDownload);
 library.add(fa.faExpandArrowsAlt);
 library.add(fa.faCompressArrowsAlt);
 library.add(fa.faLayerGroup);
@@ -597,6 +598,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
             cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
         }
+        cm.addItem({
+            description: "Download document", icon: "download", event: () => {
+                const a = document.createElement("a");
+                const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+                a.href = url;
+                a.download = `DocExport-${this.props.Document[Id]}.zip`;
+                a.click();
+            }
+        });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 1ff0e3b31..8ff7cb223 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -236,6 +236,8 @@ def parse_document(file_name: str):
         view_guids.append(write_image(pure_name, image))
         copyfile(dir_path + "/" + image, dir_path +
                  "/" + image.replace(".", "_o.", 1))
+        copyfile(dir_path + "/" + image, dir_path +
+                 "/" + image)
         os.rename(dir_path + "/" + image, dir_path +
                   "/" + image.replace(".", "_m.", 1))
     print(f"extracted {count} images...")
diff --git a/src/server/database.ts b/src/server/database.ts
index acb6ce751..a7254fb0c 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -17,7 +17,7 @@ export class Database {
         });
     }
 
-    public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) {
+    public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
         if (this.db) {
             let collection = this.db.collection(collectionName);
             const prom = this.currentWrites[id];
@@ -30,7 +30,7 @@ export class Database {
                                 delete this.currentWrites[id];
                             }
                             resolve();
-                            callback();
+                            callback(err, res);
                         });
                 });
             };
@@ -41,6 +41,30 @@ export class Database {
         }
     }
 
+    public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
+        if (this.db) {
+            let collection = this.db.collection(collectionName);
+            const prom = this.currentWrites[id];
+            let newProm: Promise<void>;
+            const run = (): Promise<void> => {
+                return new Promise<void>(resolve => {
+                    collection.replaceOne({ _id: id }, value, { upsert }
+                        , (err, res) => {
+                            if (this.currentWrites[id] === newProm) {
+                                delete this.currentWrites[id];
+                            }
+                            resolve();
+                            callback(err, res);
+                        });
+                });
+            };
+            newProm = prom ? prom.then(run) : run();
+            this.currentWrites[id] = newProm;
+        } else {
+            this.onConnect.push(() => this.replace(id, value, callback, upsert, collectionName));
+        }
+    }
+
     public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
     public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
     public delete(id: any, collectionName = Database.DocumentsCollection) {
@@ -126,7 +150,7 @@ export class Database {
         }
     }
 
-    public async visit(ids: string[], fn: (result: any) => string[], collectionName = "newDocuments") {
+    public async visit(ids: string[], fn: (result: any) => string[], collectionName = "newDocuments"): Promise<void> {
         if (this.db) {
             const visited = new Set<string>();
             while (ids.length) {
@@ -145,7 +169,12 @@ export class Database {
             }
 
         } else {
-            this.onConnect.push(() => this.visit(ids, fn, collectionName));
+            return new Promise(res => {
+                this.onConnect.push(() => {
+                    this.visit(ids, fn, collectionName);
+                    res();
+                });
+            });
         }
     }
 
diff --git a/src/server/index.ts b/src/server/index.ts
index 230c574cf..2fa5132d0 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -27,6 +27,7 @@ import { Client } from './Client';
 import { Database } from './database';
 import { MessageStore, Transferable, Types, Diff, Message } from "./Message";
 import { RouteStore } from './RouteStore';
+import v4 = require('uuid/v4');
 const app = express();
 const config = require('../../webpack.config');
 import { createCanvas, loadImage, Canvas } from "canvas";
@@ -39,7 +40,10 @@ import c = require("crypto");
 import { Search } from './Search';
 import { debug } from 'util';
 import _ = require('lodash');
+import * as Archiver from 'archiver';
+import * as AdmZip from 'adm-zip';
 import { Response } from 'express-serve-static-core';
+import { DocComponent } from '../client/views/DocComponent';
 const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 const probe = require("probe-image-size");
@@ -177,16 +181,21 @@ function msToTime(duration: number) {
     return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds;
 }
 
-app.get("/serializeDoc/:docId", async (req, res) => {
-    const files: { [name: string]: string[] } = {};
+async function getDocs(id: string) {
+    const files = new Set<string>();
     const docs: { [id: string]: any } = {};
     const fn = (doc: any): string[] => {
+        const id = doc.id;
+        if (typeof id === "string" && id.endsWith("Proto")) {
+            //Skip protos
+            return [];
+        }
         const ids: string[] = [];
-        for (const key in doc) {
-            if (!doc.hasOwnProperty(key)) {
+        for (const key in doc.fields) {
+            if (!doc.fields.hasOwnProperty(key)) {
                 continue;
             }
-            const field = doc[key];
+            const field = doc.fields[key];
             if (field === undefined || field === null) {
                 continue;
             }
@@ -194,7 +203,7 @@ app.get("/serializeDoc/:docId", async (req, res) => {
             if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
                 ids.push(field.fieldId);
             } else if (field.__type === "list") {
-                ids.push(...fn(field.fields));
+                ids.push(...fn(field));
             } else if (typeof field === "string") {
                 const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
                 let match: string[] | null;
@@ -215,35 +224,120 @@ app.get("/serializeDoc/:docId", async (req, res) => {
                 while ((match = re2.exec(field.Data)) !== null) {
                     const urlString = match[1];
                     const pathname = new URL(urlString).pathname;
-                    const ext = path.extname(pathname);
-                    const fileName = path.basename(pathname, ext);
-                    let exts = files[fileName];
-                    if (!exts) {
-                        files[fileName] = exts = [];
-                    }
-                    exts.push(ext);
+                    files.add(pathname);
                 }
             } else if (["audio", "image", "video", "pdf", "web"].includes(field.__type)) {
                 const url = new URL(field.url);
                 const pathname = url.pathname;
-                const ext = path.extname(pathname);
-                const fileName = path.basename(pathname, ext);
-                let exts = files[fileName];
-                if (!exts) {
-                    files[fileName] = exts = [];
-                }
-                exts.push(ext);
+                files.add(pathname);
             }
         }
 
-        docs[doc.id] = doc;
+        if (doc.id) {
+            docs[doc.id] = doc;
+        }
         return ids;
     };
-    Database.Instance.visit([req.params.docId], fn);
+    await Database.Instance.visit([id], fn);
+    return { id, docs, files };
+}
+app.get("/serializeDoc/:docId", async (req, res) => {
+    const { docs, files } = await getDocs(req.params.docId);
+    res.send({ docs, files: Array.from(files) });
 });
 
-app.get("/downloadId/:docId", (req, res) => {
-    res.download(`/serializeDoc/${req.params.docId}`, `DocumentExport.zip`);
+app.get("/downloadId/:docId", async (req, res) => {
+    res.set('Content-disposition', `attachment;`);
+    res.set('Content-Type', "application/zip");
+    const { id, docs, files } = await getDocs(req.params.docId);
+    const docString = JSON.stringify({ id, docs });
+    const zip = Archiver('zip');
+    zip.pipe(res);
+    zip.append(docString, { name: "doc.json" });
+    files.forEach(val => {
+        zip.file(__dirname + RouteStore.public + val, { name: val.substring(1) });
+    });
+    zip.finalize();
+});
+
+app.post("/uploadDoc", (req, res) => {
+    let form = new formidable.IncomingForm();
+    form.keepExtensions = true;
+    // let path = req.body.path;
+    const ids: { [id: string]: string } = {};
+    let remap = true;
+    const getId = (id: string): string => {
+        if (!remap) return id;
+        if (id.endsWith("Proto")) return id;
+        if (id in ids) {
+            return ids[id];
+        } else {
+            return ids[id] = v4();
+        }
+    };
+    const mapFn = (doc: any) => {
+        if (doc.id) {
+            doc.id = getId(doc.id);
+        }
+        for (const key in doc.fields) {
+            if (!doc.fields.hasOwnProperty(key)) {
+                continue;
+            }
+            const field = doc.fields[key];
+            if (field === undefined || field === null) {
+                continue;
+            }
+
+            if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+                field.fieldId = getId(field.fieldId);
+            } else if (field.__type === "list") {
+                mapFn(field);
+            } else if (typeof field === "string") {
+                const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g;
+                doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => {
+                    return `${p1}${getId(p2)}"`;
+                });
+            } else if (field.__type === "RichTextField") {
+                const re = /("href"\s*:\s*")(.*?)"/g;
+                field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => {
+                    return `${p1}${getId(p2)}"`;
+                });
+            }
+        }
+    };
+    form.parse(req, async (err, fields, files) => {
+        remap = fields.remap !== "false";
+        let id: string = "";
+        try {
+            for (const name in files) {
+                const path = files[name].path;
+                const zip = new AdmZip(path);
+                zip.getEntries().forEach(entry => {
+                    if (!entry.name.startsWith("files/")) return;
+                    zip.extractEntryTo(entry.name, __dirname + RouteStore.public, true, false);
+                });
+                const json = zip.getEntry("doc.json");
+                let docs: any;
+                try {
+                    let data = JSON.parse(json.getData().toString("utf8"));
+                    docs = data.docs;
+                    id = data.id;
+                    docs = Object.keys(docs).map(key => docs[key]);
+                    docs.forEach(mapFn);
+                    await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => {
+                        err && console.log(err);
+                        res();
+                    }, true, "newDocuments"))));
+                } catch (e) { console.log(e); }
+                fs.unlink(path, () => { });
+            }
+            if (id) {
+                res.send(JSON.stringify(getId(id)));
+            } else {
+                res.send(JSON.stringify("error"));
+            }
+        } catch (e) { console.log(e); }
+    });
 });
 
 app.get("/whosOnline", (req, res) => {
-- 
cgit v1.2.3-70-g09d2


From f953ab92c8e0d4bf54f90a6b7f22a86cf5985c38 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 30 Jul 2019 22:39:52 -0400
Subject: removed chrome (scrollbar) of collectionChrome when collapsed.  fixed
 make background.

---
 src/client/views/collections/CollectionView.tsx          | 14 ++++++--------
 src/client/views/collections/CollectionViewChromes.scss  |  1 +
 src/client/views/collections/CollectionViewChromes.tsx   |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx        |  6 +++++-
 .../views/collections/collectionFreeForm/MarqueeView.tsx | 16 +++++++++++++---
 src/client/views/nodes/DocumentView.tsx                  |  9 +++++----
 6 files changed, 31 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 376aaf53f..34adc5840 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,11 +1,13 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV, faFingerprint, faLaptopCode } from '@fortawesome/free-solid-svg-icons';
+import { faEye } from '@fortawesome/free-regular-svg-icons';
+import { faColumns, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
-import { Doc, DocListCast, WidthSym, HeightSym } from '../../../new_fields/Doc';
+import { Doc } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
+import { StrCast } from '../../../new_fields/Types';
 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';
@@ -15,11 +17,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
-import { StrCast, PromiseValue } from '../../../new_fields/Types';
-import { DocumentType } from '../../documents/Documents';
-import { CollectionStackingViewChrome, CollectionViewBaseChrome } from './CollectionViewChromes';
-import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
+import { CollectionViewBaseChrome } from './CollectionViewChromes';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 0c2665b1c..793cb7a8b 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -14,6 +14,7 @@
         grid-template-columns: 1fr auto;
         padding-bottom: 10px;
         border-bottom: .5px solid rgb(180, 180, 180);
+        overflow: hidden;
 
         .collectionViewBaseChrome {
             display: flex;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index ecf78b8c1..1b2561953 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -213,7 +213,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
                         </select>
-                        <div className="collectionViewBaseChrome-viewSpecs">
+                        <div className="collectionViewBaseChrome-viewSpecs" style={{ display: this._collapsed ? "none" : "grid" }}>
                             <input className="collectionViewBaseChrome-viewSpecsInput"
                                 placeholder="FILTER DOCUMENTS"
                                 value={this.filterValue ? this.filterValue.script.originalScript : ""}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cbab14976..3cead34e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -310,7 +310,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onDragOver = (): void => {
     }
 
-    bringToFront = (doc: Doc) => {
+    bringToFront = (doc: Doc, sendToBack?: boolean) => {
+        if (sendToBack) {
+            doc.zIndex = 0;
+            return;
+        }
         const docs = this.childDocs;
         docs.slice().sort((doc1, doc2) => {
             if (doc1 === doc) return 1;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 07d06d053..b9ee588dd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -370,15 +370,25 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let selRect = this.Bounds;
         let selection: Doc[] = [];
         this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
-            var z = NumCast(doc.zoomBasis, 1);
             var x = NumCast(doc.x);
             var y = NumCast(doc.y);
-            var w = NumCast(doc.width) / z;
-            var h = NumCast(doc.height) / z;
+            var w = NumCast(doc.width);
+            var h = NumCast(doc.height);
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
         });
+        if (!selection.length) {
+            this.props.activeDocuments().map(doc => {
+                var x = NumCast(doc.x);
+                var y = NumCast(doc.y);
+                var w = NumCast(doc.width);
+                var h = NumCast(doc.height);
+                if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+                    selection.push(doc);
+                }
+            });
+        }
         return selection;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 51662274d..14f0b5a5a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -96,7 +96,7 @@ export interface DocumentViewProps {
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-    bringToFront: (doc: Doc) => void;
+    bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
@@ -529,7 +529,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     makeBackground = (): void => {
-        this.props.Document.isBackground = true;
+        this.props.Document.isBackground = !this.props.Document.isBackground;
+        this.props.Document.isBackground && this.props.bringToFront(this.props.Document, true);
     }
 
     @undoBatch
@@ -568,7 +569,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         cm.addItem({ description: "Transcribe Speech", event: this.listen, icon: "microphone" });
         let makes: ContextMenuProps[] = [];
-        makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+        makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         makes.push({
             description: "Make Portal", event: () => {
@@ -708,7 +709,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
-                    pointerEvents: this.layoutDoc.isBackground ? "none" : "all",
+                    pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-- 
cgit v1.2.3-70-g09d2


From 7ebbcc8e09933150d4b5b5daf19cb781c99a8f8f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 30 Jul 2019 23:52:44 -0400
Subject: fixed collection chrome with tree views.  fixed fitToBounds
 calculation

---
 .../views/collections/CollectionTreeView.scss      |  3 ++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 37 +++++++++++-----------
 2 files changed, 21 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index db3652ff6..990979109 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -7,6 +7,9 @@
     border-radius: inherit;
     box-sizing: border-box;
     height: 100%;
+    width:100%;
+    position: absolute;
+    top:0;
     padding-top: 20px;
     padding-left: 10px;
     padding-right: 0px;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3cead34e6..a1b1871fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,25 +1,35 @@
-import { action, computed, trace } from "mobx";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { action, computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
 import { emptyFunction, returnOne, Utils } from "../../../../Utils";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { DocServer } from "../../../DocServer";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
+import { CompileScript } from "../../../util/Scripting";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
-import { SubmenuProps, ContextMenuProps } from "../../ContextMenuItem";
+import { ContextMenu } from "../../ContextMenu";
+import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
 import { pageSchema } from "../../nodes/ImageBox";
+import { OverlayElementOptions, OverlayView } from "../../OverlayView";
 import PDFMenu from "../../pdf/PDFMenu";
+import { ScriptBox } from "../../ScriptBox";
 import { CollectionSubView } from "../CollectionSubView";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -27,19 +37,6 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
-import { ScriptField } from "../../../../new_fields/ScriptField";
-import { OverlayView, OverlayElementOptions } from "../../OverlayView";
-import { ScriptBox } from "../../ScriptBox";
-import { CompileScript } from "../../../util/Scripting";
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { undo } from "prosemirror-history";
-import { number } from "prop-types";
-import { ContextMenu } from "../../ContextMenu";
-import { RouteStore } from "../../../../server/RouteStore";
-import { DocServer } from "../../../DocServer";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
@@ -81,11 +78,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get contentBounds() {
         let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined;
-        return {
+        let res = {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
             scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling
         };
+        if (res.scale === 0) res.scale = 1;
+        return res;
     }
 
     @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; }
@@ -492,11 +491,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
         let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => {
             var page = NumCast(doc.page, -1);
-            let bounds: { x?: number, y?: number, width?: number, height?: number };
             if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
                 let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
-                    const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
+                    const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
+                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
                     state = pos.state === undefined ? state : pos.state;
                     prev.push({
                         ele: <CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />,
-- 
cgit v1.2.3-70-g09d2


From b6fa309cea934d250fe992e70e1e268f344659b5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 31 Jul 2019 00:19:13 -0400
Subject: fixed transitions on drag

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a1b1871fe..33a584c6f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -498,7 +498,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
                     state = pos.state === undefined ? state : pos.state;
                     prev.push({
-                        ele: <CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />,
+                        ele: <CollectionFreeFormDocumentView key={doc[Id]}
+                            x={script ? pos.x : undefined} y={script ? pos.y : undefined}
+                            width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
                         bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined
                     });
                 }
-- 
cgit v1.2.3-70-g09d2


From 49faa4e76f91fed04bb1923d81dd23d57a157a63 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 31 Jul 2019 07:35:41 -0400
Subject: grouping experiment

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 49 +++++++++++++++++++++-
 src/client/views/nodes/DocumentView.tsx            |  1 +
 2 files changed, 49 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8dac785e1..c4c3ba084 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,6 +38,7 @@ import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsA
 import { undo } from "prosemirror-history";
 import { number } from "prop-types";
 import { ContextMenu } from "../../ContextMenu";
+import { SwatchesPicker } from "react-color";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass);
 
@@ -125,7 +126,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
     }
 
-
+    intersectRect(r1: { left: number, top: number, width: number, height: number },
+        r2: { left: number, top: number, width: number, height: number }) {
+        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+    }
+    bounsdSelect(doc: Doc, doc2: Doc) {
+        var x2 = NumCast(doc2.x) - 25;
+        var y2 = NumCast(doc2.y) - 25;
+        var w2 = NumCast(doc2.width) + 25;
+        var h2 = NumCast(doc2.height) + 25;
+        var x = NumCast(doc.x) - 25;
+        var y = NumCast(doc.y) - 25;
+        var w = NumCast(doc.width) + 25;
+        var h = NumCast(doc.height) + 25;
+        if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
+            return true;
+        }
+        return false;
+    }
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -151,6 +169,35 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         }
                         this.bringToFront(d);
                     });
+
+                    let sets: (Doc[])[] = []
+                    this.childDocs.map(c => {
+                        let included = []
+                        for (let i = 0; i < sets.length; i++) {
+                            for (let j = 0; j < sets[i].length; j++) {
+                                if (this.bounsdSelect(c, sets[i][j])) {
+                                    included.push(i);
+                                    break;
+                                }
+                            }
+                        }
+                        if (included.length === 0)
+                            sets.push([c]);
+                        else if (included.length === 1)
+                            sets[included[0]].push(c);
+                        else {
+                            sets[included[0]].push(c);
+                            for (let s = 1; s < included.length; s++) {
+                                sets[included[0]].push(...sets[included[s]]);
+                                sets[included[s]].length = 0;
+                            }
+                        }
+                    });
+                    for (let s = 0; s < sets.length; s++) {
+                        for (let i = 0; i < sets[s].length; i++) {
+                            Doc.GetProto(sets[s][i]).cluster = s;
+                        }
+                    }
                 }
             }
             else if (de.data instanceof DragManager.AnnotationDragData) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4b5cf3a43..2dc2c18a3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -694,6 +694,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
+                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-- 
cgit v1.2.3-70-g09d2


From a4000aa57c9934ff3a68ba17474936836a5f235e Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 10:35:02 -0400
Subject: tweaks

---
 src/client/views/ContextMenu.tsx                              |  2 +-
 src/client/views/DocumentDecorations.tsx                      |  1 -
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 11 +++++------
 src/client/views/nodes/DocumentView.tsx                       |  6 ------
 4 files changed, 6 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 98025ac31..1bf6e383d 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -42,7 +42,7 @@ export class ContextMenu extends React.Component {
         return this._items.find(menuItem => {
             let reference = menuItem.description;
             toLowerCase && (reference = reference.toLowerCase());
-            reference === target;
+            return reference === target;
         });
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index eccfc7c30..15471371a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -541,7 +541,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 if (fixedAspect && (!nwidth || !nheight)) {
                     proto.nativeWidth = nwidth = doc.width || 0;
                     proto.nativeHeight = nheight = doc.height || 0;
-                    proto.ignoreAspect = true;
                 }
                 if (nwidth > 0 && nheight > 0 && !BoolCast(proto.ignoreAspect)) {
                     if (Math.abs(dW) > Math.abs(dH)) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 33a584c6f..29f9b1429 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -131,10 +131,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         let xf = this.getTransform();
+        let [xp, yp] = xf.transformPoint(de.x, de.y);
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let [xp, yp] = xf.transformPoint(de.x, de.y);
                     let x = xp - de.data.xOffset;
                     let y = yp - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
@@ -157,7 +157,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             else if (de.data instanceof DragManager.AnnotationDragData) {
                 if (de.data.dropDocument) {
                     let dragDoc = de.data.dropDocument;
-                    let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
                     let x = xp - de.data.xOffset;
                     let y = yp - de.data.yOffset;
                     let dropX = NumCast(de.data.dropDocument.x);
@@ -224,10 +223,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     this._pheight / this.zoomScaling());
                 let panelwidth = panelDim[0];
                 let panelheight = panelDim[1];
-                // if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
-                // if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
-                // if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
-                // if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
+                if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
+                if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
+                if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
+                if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
             }
             this.setPan(x - dx, y - dy);
             this._lastX = e.pageX;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 14f0b5a5a..d4adcfca1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -586,12 +586,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }, icon: "window-restore"
         });
         cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
-        // cm.addItem({
-        //     description: "Find aliases", event: async () => {
-        //         const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-        //         this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
-        //     }, icon: "search"
-        // });
         if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
             cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
         }
-- 
cgit v1.2.3-70-g09d2


From 6f3d4a7015e15e0523fc194f7f911f6d45259165 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 13:57:59 -0400
Subject: added floating docs

---
 src/client/util/DragManager.ts                     |  7 +++-
 src/client/views/TemplateMenu.tsx                  | 44 ++++++++++++++++++++-
 .../collectionFreeForm/CollectionFreeFormView.scss |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 46 +++++++++++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 +
 src/client/views/nodes/DocumentView.tsx            | 42 +++++++++-----------
 6 files changed, 100 insertions(+), 41 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index abcc3a4e1..0b5c785a4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -10,6 +10,7 @@ import { LinkManager } from "./LinkManager";
 import { SelectionManager } from "./SelectionManager";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { DocumentDecorations } from "../views/DocumentDecorations";
+import { NumberLiteralType } from "typescript";
 
 export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(
@@ -140,6 +141,10 @@ export namespace DragManager {
         dragHasStarted?: () => void;
 
         withoutShiftDrag?: boolean;
+
+        offsetX?: number;
+
+        offsetY?: number;
     }
 
     export interface DragDropDisposer {
@@ -423,7 +428,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) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
             );
         };
 
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1b32f0ddd..c413650f0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -7,6 +7,10 @@ import { DocumentView } from "./nodes/DocumentView";
 import { Template } from "./Templates";
 import React = require("react");
 import { undoBatch } from "../util/UndoManager";
+import { DocumentManager } from "../util/DocumentManager";
+import { NumCast } from "../../new_fields/Types";
+import { DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -35,11 +39,46 @@ export interface TemplateMenuProps {
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
+    dragRef = React.createRef<HTMLDivElement>();
 
     constructor(props: TemplateMenuProps) {
         super(props);
     }
 
+    toggleFloat = (e: React.MouseEvent): void => {
+        SelectionManager.DeselectAll();
+        let topDocView = this.props.docs[0];
+        let topDoc = topDocView.props.Document;
+        let xf = topDocView.props.ScreenToLocalTransform();
+        let ex = e.clientX;
+        let ey = e.clientY;
+        undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
+        if (!topDoc.z) {
+            setTimeout(() => {
+                let newDocView = DocumentManager.Instance.getDocumentView(topDoc);
+                if (newDocView) {
+                    let de = new DragManager.DocumentDragData([topDoc], [undefined]);
+                    de.moveDocument = topDocView.props.moveDocument;
+                    let xf = newDocView.ContentDiv!.getBoundingClientRect();
+                    console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
+                    DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
+                        offsetX: (ex - xf.left), offsetY: (ey - xf.top),
+                        handlers: {
+                            dragComplete: () => { },
+                        },
+                        hideSource: false
+                    });
+                }
+            }, 10);
+        } else if (topDocView.props.ContainingCollectionView) {
+            let collView = topDocView.props.ContainingCollectionView!;
+            let [sx, sy] = xf.inverse().transformPoint(0, 0);
+            let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
+            topDoc.x = x;
+            topDoc.y = y;
+        }
+    }
+
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -89,9 +128,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         return (
             <div className="templating-menu" >
                 <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
-                <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+                <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
-                    <button style={{ display: this._hidden ? "none" : "block" }} onClick={this.clearTemplates}>Clear</button>
+                    <button onClick={this.toggleFloat}>Float</button>
+                    <button onClick={this.clearTemplates}>Clear</button>
                 </ul>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index cca199afa..c4311fa52 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -46,6 +46,7 @@
     border-radius: inherit;
     box-sizing: border-box;
     position: absolute;
+    overflow: hidden;
 
     .marqueeView {
         overflow: hidden;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 29f9b1429..9cbc652b9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -77,7 +77,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined;
+        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
         let res = {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -98,6 +98,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
     private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+    private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
@@ -131,12 +132,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         let xf = this.getTransform();
+        let xfo = this.getTransformOverlay();
         let [xp, yp] = xf.transformPoint(de.x, de.y);
+        let [xpo, ypo] = xfo.transformPoint(de.x, de.y);
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let x = xp - de.data.xOffset;
-                    let y = yp - de.data.yOffset;
+                    let z = NumCast(de.data.draggedDocuments[0].z);
+                    let x = (z ? xpo : xp) - de.data.xOffset;
+                    let y = (z ? ypo : yp) - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
                     let dropY = NumCast(de.data.droppedDocuments[0].y);
                     de.data.droppedDocuments.forEach(d => {
@@ -292,8 +296,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
         this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
         this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY;
-        // this.props.Document.panX = panX;
-        // this.props.Document.panY = panY;
         if (this.props.Document.scrollY) {
             this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
         }
@@ -396,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
-            ScreenToLocalTransform: this.getTransform,
+            ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
             selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
             PanelWidth: pair.layout[WidthSym],
@@ -436,23 +438,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } {
+    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } {
         const result = script.script.run(params);
         if (!result.success) {
             return {};
         }
-        return result.result === undefined ? {} : result.result;
+        let doc = params.doc;
+        return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result;
     }
 
-    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined {
+    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } } | undefined {
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string");
             const x = Cast(viewDef.x, "number");
             const y = Cast(viewDef.y, "number");
+            const z = Cast(viewDef.z, "number");
             const width = Cast(viewDef.width, "number");
             const height = Cast(viewDef.height, "number");
             const fontSize = Cast(viewDef.fontSize, "number");
-            if ([text, x, y, width, height].some(val => val === undefined)) {
+            if ([text, x, y, z, width, height].some(val => val === undefined)) {
                 return undefined;
             }
 
@@ -460,7 +464,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 ele: <div className="collectionFreeform-customText" style={{
                     transform: `translate(${x}px, ${y}px)`,
                     width, height, fontSize
-                }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! }
+                }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
             };
         }
     }
@@ -472,7 +476,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const script = this.Document.arrangeScript;
         let state: any = undefined;
         const docs = this.childDocs;
-        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = [];
+        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } }[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
@@ -494,13 +498,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
-                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
+                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
                     state = pos.state === undefined ? state : pos.state;
                     prev.push({
                         ele: <CollectionFreeFormDocumentView key={doc[Id]}
                             x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                             width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
-                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined
+                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined
                     });
                 }
             }
@@ -514,8 +518,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed.struct
     get views() {
-        return this.elements.map(ele => ele.ele);
+        return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
     }
+    @computed.struct
+    get overlayViews() {
+        return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+    }
+
 
     @action
     onCursorMove = (e: React.PointerEvent) => {
@@ -603,6 +612,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
     ]
+    private overlayChildViews = () => {
+        console.log(this.overlayViews.length);
+        return [
+            ...this.overlayViews
+        ];
+    }
 
     public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
         return () => {
@@ -651,6 +666,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
+                {this.overlayChildViews()}
                 <CollectionFreeFormOverlayView  {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
             </div>
         );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ffd760e0..3b6c443c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,6 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
+                    boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d4adcfca1..45b5c328c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,19 +1,24 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import * as rp from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
+import { RouteStore } from '../../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentType } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ClientUtils } from '../../util/ClientUtils';
+import DictationManager from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
-import { SearchUtil } from "../../util/SearchUtil";
+import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -22,27 +27,17 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
+import { EditableView } from '../EditableView';
+import { OverlayView } from '../OverlayView';
 import { PresentationView } from "../presentationview/PresentationView";
-import { Template, Templates } from "./../Templates";
+import { ScriptingRepl } from '../ScriptingRepl';
+import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
-import * as rp from "request-promise";
 import "./DocumentView.scss";
-import React = require("react");
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { list, object, createSimpleSchema } from 'serializr';
-import { LinkManager } from '../../util/LinkManager';
-import { RouteStore } from '../../../server/RouteStore';
 import { FormattedTextBox } from './FormattedTextBox';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
-import { ClientUtils } from '../../util/ClientUtils';
-import { EditableView } from '../EditableView';
-import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
-import { DocumentDecorations } from '../DocumentDecorations';
-import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
-import DictationManager from '../../util/DictationManager';
+import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -121,6 +116,7 @@ export const positionSchema = createSchema({
     height: "number",
     x: "number",
     y: "number",
+    z: "number",
 });
 
 export type PositionDocument = makeInterface<[typeof positionSchema]>;
-- 
cgit v1.2.3-70-g09d2


From 65f23d993c091ee7fa61becea85b5f60ec2e9104 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 15:44:22 -0400
Subject: added dragging of clusters.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 37 +++++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 2 files changed, 31 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8cac60edb..12e5924b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -131,15 +131,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
+    _groupingBorder = 150;
     bounsdSelect(doc: Doc, doc2: Doc) {
-        var x2 = NumCast(doc2.x) - 25;
-        var y2 = NumCast(doc2.y) - 25;
-        var w2 = NumCast(doc2.width) + 25;
-        var h2 = NumCast(doc2.height) + 25;
-        var x = NumCast(doc.x) - 25;
-        var y = NumCast(doc.y) - 25;
-        var w = NumCast(doc.width) + 25;
-        var h = NumCast(doc.height) + 25;
+        var x2 = NumCast(doc2.x) - this._groupingBorder;
+        var y2 = NumCast(doc2.y) - this._groupingBorder;
+        var w2 = NumCast(doc2.width) + this._groupingBorder;
+        var h2 = NumCast(doc2.height) + this._groupingBorder;
+        var x = NumCast(doc.x) - this._groupingBorder;
+        var y = NumCast(doc.y) - this._groupingBorder;
+        var w = NumCast(doc.width) + this._groupingBorder;
+        var h = NumCast(doc.height) + this._groupingBorder;
         if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
             return true;
         }
@@ -242,6 +243,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
+            let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
+            let cluster = this.childDocs.reduce((cluster, cd) => {
+                let cx = NumCast(cd.x) - this._groupingBorder;
+                let cy = NumCast(cd.y) - this._groupingBorder;
+                let cw = NumCast(cd.width) + this._groupingBorder;
+                let ch = NumCast(cd.height) + this._groupingBorder;
+                if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+                    return NumCast(cd.cluster);
+                return cluster;
+            }, -1);
+            if (cluster !== -1) {
+                let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+                this.selectDocuments(eles);
+                DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(v => v.ContentDiv!), new DragManager.DocumentDragData(eles, eles.map(d => undefined)), e.clientX, e.clientY);
+                e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
+                e.preventDefault();
+                document.removeEventListener("pointermove", this.onPointerMove);
+                document.removeEventListener("pointerup", this.onPointerUp);
+                return;
+            }
             let x = this.Document.panX || 0;
             let y = this.Document.panY || 0;
             let docs = this.childDocs || [];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c5cf2e23a..fa1e824f8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -695,6 +695,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
+        let colors = ["red", "green", "blue", "purple", "orange", "cyan", "black"];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -703,7 +704,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
+                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-- 
cgit v1.2.3-70-g09d2


From 64d25bd7a92daa4a8b9ee69f0eabd88e5cbeeca5 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 16:04:43 -0400
Subject: cleaned up detaillayout for pivot viewer.

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx    | 2 +-
 src/new_fields/Doc.ts                                            | 9 ++-------
 2 files changed, 3 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9cbc652b9..90f67c54e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -456,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const width = Cast(viewDef.width, "number");
             const height = Cast(viewDef.height, "number");
             const fontSize = Cast(viewDef.fontSize, "number");
-            if ([text, x, y, z, width, height].some(val => val === undefined)) {
+            if ([text, x, y, width, height].some(val => val === undefined)) {
                 return undefined;
             }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6e2c5e697..b70951040 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -525,19 +525,14 @@ export namespace Doc {
     }
     export function UseDetailLayout(d: Doc) {
         runInAction(async () => {
-            const dl1 = d.detailedLayout;
-            let detailLayout1 = await PromiseValue(dl1);
-            const dl2 = d.detailedLayout;
-            let detailLayout = await PromiseValue(dl2);
+            let detailLayout = await d.detailedLayout;
             if (detailLayout) {
                 d.layout = detailLayout;
                 d.nativeWidth = d.nativeHeight = undefined;
                 if (detailLayout instanceof Doc) {
                     let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc;
                     d.layout = delegDetailLayout;
-                    let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout);
-                    let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout);
-                    delegDetailLayout.layout = subDetailLayout;
+                    delegDetailLayout.layout = await delegDetailLayout.detailedLayout;
                 }
             }
         });
-- 
cgit v1.2.3-70-g09d2


From 8cd098ab0e348c614640bc0971f3859161b8c2b4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 2 Aug 2019 16:39:03 -0400
Subject: fixed drag interactions with groups.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx      | 18 ++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx                |  4 ++--
 2 files changed, 18 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 12e5924b4..3ff816c46 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -131,7 +131,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
-    _groupingBorder = 150;
+    _groupingBorder = 100;
     bounsdSelect(doc: Doc, doc2: Doc) {
         var x2 = NumCast(doc2.x) - this._groupingBorder;
         var y2 = NumCast(doc2.y) - this._groupingBorder;
@@ -256,7 +256,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (cluster !== -1) {
                 let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
                 this.selectDocuments(eles);
-                DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(v => v.ContentDiv!), new DragManager.DocumentDragData(eles, eles.map(d => undefined)), e.clientX, e.clientY);
+                let clusterDocs = SelectionManager.SelectedDocuments();
+                SelectionManager.DeselectAll();
+                let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+                de.moveDocument = this.props.moveDocument;
+                const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
+                const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
+                de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+                de.xOffset = xoff;
+                de.yOffset = yoff;
+                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
+                    handlers: {
+                        dragComplete: action(emptyFunction)
+                    },
+                    hideSource: !de.dropAction
+                });
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
                 document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fa1e824f8..d79b58a7b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -695,7 +695,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["red", "green", "blue", "purple", "orange", "cyan", "black"];
+        let colors = ["#da42429e", "#31ea318c", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "#000000ad"];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -704,7 +704,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
+                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-- 
cgit v1.2.3-70-g09d2


From fe9017c3cdc2481f4bcce40dcc1514a6f7af37dc Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 2 Aug 2019 18:21:23 -0400
Subject: improvements to grouping with backgrounds

---
 .../views/collections/CollectionBaseView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 68 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  9 ++-
 3 files changed, 44 insertions(+), 35 deletions(-)

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

diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c595a4c56..6801b94fd 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -147,7 +147,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             <div id="collectionBaseView"
                 style={{
                     pointerEvents: this.props.Document.isBackground ? "none" : "all",
-                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
+                    boxShadow: this.props.Document.isBackground ? undefined : `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
                 }}
                 className={this.props.className || "collectionView-cont"}
                 onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 232ac22c9..bc1aee159 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -108,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
         this.bringToFront(newBox);
+        this.updateClusters();
         return true;
     }
     private selectDocuments = (docs: Doc[]) => {
@@ -175,34 +176,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         this.bringToFront(d);
                     });
 
-                    let sets: (Doc[])[] = []
-                    this.childDocs.map(c => {
-                        let included = []
-                        for (let i = 0; i < sets.length; i++) {
-                            for (let j = 0; j < sets[i].length; j++) {
-                                if (this.bounsdSelect(c, sets[i][j])) {
-                                    included.push(i);
-                                    break;
-                                }
-                            }
-                        }
-                        if (included.length === 0)
-                            sets.push([c]);
-                        else if (included.length === 1)
-                            sets[included[0]].push(c);
-                        else {
-                            sets[included[0]].push(c);
-                            for (let s = 1; s < included.length; s++) {
-                                sets[included[0]].push(...sets[included[s]]);
-                                sets[included[s]].length = 0;
-                            }
-                        }
-                    });
-                    for (let s = 0; s < sets.length; s++) {
-                        for (let i = 0; i < sets[s].length; i++) {
-                            Doc.GetProto(sets[s][i]).cluster = s;
-                        }
-                    }
+                    this.updateClusters();
                 }
             }
             else if (de.data instanceof DragManager.AnnotationDragData) {
@@ -223,6 +197,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
+    @action
+    updateClusters() {
+        let sets: (Doc[])[] = []
+        this.childDocs.map(c => {
+            let included = []
+            for (let i = 0; i < sets.length; i++) {
+                for (let j = 0; j < sets[i].length; j++) {
+                    if (this.bounsdSelect(c, sets[i][j])) {
+                        included.push(i);
+                        break;
+                    }
+                }
+            }
+            if (included.length === 0)
+                sets.push([c]);
+            else if (included.length === 1)
+                sets[included[0]].push(c);
+            else {
+                sets[included[0]].push(c);
+                for (let s = 1; s < included.length; s++) {
+                    sets[included[0]].push(...sets[included[s]]);
+                    sets[included[s]].length = 0;
+                }
+            }
+        });
+        for (let s = 0; s < sets.length; s++) {
+            for (let i = 0; i < sets[s].length; i++) {
+                Doc.GetProto(sets[s][i]).cluster = s;
+            }
+        }
+    }
+
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
@@ -247,8 +253,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let cluster = this.childDocs.reduce((cluster, cd) => {
                 let cx = NumCast(cd.x) - this._groupingBorder;
                 let cy = NumCast(cd.y) - this._groupingBorder;
-                let cw = NumCast(cd.width) + this._groupingBorder;
-                let ch = NumCast(cd.height) + this._groupingBorder;
+                let cw = NumCast(cd.width) + 2 * this._groupingBorder;
+                let ch = NumCast(cd.height) + 2 * this._groupingBorder;
                 if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
                     return NumCast(cd.cluster);
                 return cluster;
@@ -392,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     bringToFront = (doc: Doc, sendToBack?: boolean) => {
-        if (sendToBack) {
+        if (sendToBack || doc.isBackground) {
             doc.zIndex = 0;
             return;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d79b58a7b..16ae5471d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -695,7 +695,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["#da42429e", "#31ea318c", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "#000000ad"];
+        let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+        let groupCol = colors[NumCast(this.props.Document.cluster) % colors.length];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -704,7 +705,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
+                    boxShadow: this.layoutDoc.isBackground ?
+                        `0px 0px 50px 50px ${groupCol}` :
+                        `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
@@ -714,7 +717,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
-                    background: backgroundColor,
+                    background: this.layoutDoc.isBackground ? groupCol : backgroundColor,
                     width: nativeWidth,
                     height: nativeHeight,
                     transform: `scale(${this.props.ContentScaling()})`,
-- 
cgit v1.2.3-70-g09d2


From 753615c80d4cf08605ebaaeeaf0a44a0fd88d898 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 3 Aug 2019 15:21:10 -0400
Subject: working version of clustering

---
 src/Utils.ts                                       |   2 +
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/DragManager.ts                     |   8 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/TemplateMenu.tsx                  |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   3 +-
 .../views/collections/CollectionSchemaView.tsx     |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 142 +++++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |   1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  12 +-
 src/client/views/nodes/DocumentView.tsx            |  17 +--
 .../views/presentationview/PresentationElement.tsx |   3 +-
 src/client/views/search/SearchItem.tsx             |   3 +-
 13 files changed, 125 insertions(+), 81 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 8df67df5d..502540eb0 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -140,6 +140,8 @@ export function returnOne() { return 1; }
 
 export function returnZero() { return 0; }
 
+export function returnEmptyString() { return ""; }
+
 export function emptyFunction() { }
 
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 09bafcf43..07e38a4c0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -84,6 +84,7 @@ export interface DocumentOptions {
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
+    defaultBackgroundColor?: string;
     dropAction?: dropActionType;
     backgroundLayout?: string;
     chromeStatus?: string;
@@ -124,7 +125,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox },
-                options: { height: 150, backgroundColor: "#f1efeb" }
+                options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, collectionView: [CollectionView, data] as CollectionViewType },
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0b5c785a4..a7aaaed7c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -404,7 +404,8 @@ export namespace DragManager {
                 hideSource = options.hideSource();
             }
         }
-        eles.map(ele => (ele.hidden = hideSource));
+        eles.map(ele => (ele.hidden = hideSource) &&
+            (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = hideSource)));
 
         let lastX = downX;
         let lastY = downY;
@@ -434,7 +435,10 @@ export namespace DragManager {
 
         let hideDragElements = () => {
             dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
-            eles.map(ele => (ele.hidden = false));
+            eles.map(ele => {
+                ele.hidden = false;
+                (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = false));
+            });
         };
         let endDrag = () => {
             document.removeEventListener("pointermove", moveHandler, true);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2ecf5fd85..53f589684 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
@@ -270,6 +270,7 @@ export class MainView extends React.Component {
                             PanelWidth={this.getPWidth}
                             PanelHeight={this.getPHeight}
                             renderDepth={0}
+                            backgroundColor={returnEmptyString}
                             selectOnLoad={false}
                             focus={emptyFunction}
                             parentActive={returnTrue}
@@ -334,6 +335,7 @@ export class MainView extends React.Component {
             renderDepth={0}
             selectOnLoad={false}
             focus={emptyFunction}
+            backgroundColor={returnEmptyString}
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             bringToFront={emptyFunction}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e654a0644..6dd908445 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -60,12 +60,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     let de = new DragManager.DocumentDragData([topDoc], [undefined]);
                     de.moveDocument = topDocView.props.moveDocument;
                     let xf = newDocView.ContentDiv!.getBoundingClientRect();
-                    console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
                     DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
                         offsetX: (ex - xf.left), offsetY: (ey - xf.top),
-                        handlers: {
-                            dragComplete: () => { },
-                        },
+                        handlers: { dragComplete: () => { }, },
                         hideSource: false
                     });
                 }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 588102f01..f559480ed 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -10,7 +10,7 @@ import { Id } from '../../../new_fields/FieldSymbols';
 import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { emptyFunction, returnTrue, Utils, returnOne } from "../../../Utils";
+import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
@@ -607,6 +607,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             focus={emptyFunction}
+            backgroundColor={returnEmptyString}
             addDocTab={this.addDocTab}
             ContainingCollectionView={undefined}
             zoomToScale={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9efd0d3ec..8218877ba 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -6,7 +6,7 @@ import { action, computed, observable, trace, untracked } from "mobx";
 import { observer } from "mobx-react";
 import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize } from "react-table";
 import "react-table/react-table.css";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
@@ -999,6 +999,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         PanelHeight={this.PanelHeight}
                         ContainingCollectionView={this.props.CollectionView}
                         focus={emptyFunction}
+                        backgroundColor={returnEmptyString}
                         parentActive={this.props.active}
                         whenActiveChanged={this.props.whenActiveChanged}
                         bringToFront={emptyFunction}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bc1aee159..ee21c5a4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -9,7 +9,7 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils } from "../../../../Utils";
+import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { DocumentManager } from "../../../util/DocumentManager";
@@ -37,6 +37,8 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { setScheduler } from "bluebird";
+import { DocumentType, Docs } from "../../../documents/Documents";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
@@ -132,17 +134,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
-    _groupingBorder = 100;
+    _clusterDistance = 75;
     bounsdSelect(doc: Doc, doc2: Doc) {
-        var x2 = NumCast(doc2.x) - this._groupingBorder;
-        var y2 = NumCast(doc2.y) - this._groupingBorder;
-        var w2 = NumCast(doc2.width) + this._groupingBorder;
-        var h2 = NumCast(doc2.height) + this._groupingBorder;
-        var x = NumCast(doc.x) - this._groupingBorder;
-        var y = NumCast(doc.y) - this._groupingBorder;
-        var w = NumCast(doc.width) + this._groupingBorder;
-        var h = NumCast(doc.height) + this._groupingBorder;
-        if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
+        var x2 = NumCast(doc2.x) - this._clusterDistance;
+        var y2 = NumCast(doc2.y) - this._clusterDistance;
+        var w2 = NumCast(doc2.width) + this._clusterDistance;
+        var h2 = NumCast(doc2.height) + this._clusterDistance;
+        var x = NumCast(doc.x) - this._clusterDistance;
+        var y = NumCast(doc.y) - this._clusterDistance;
+        var w = NumCast(doc.width) + this._clusterDistance;
+        var h = NumCast(doc.height) + this._clusterDistance;
+        if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
             return true;
         }
         return false;
@@ -197,36 +199,83 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
+    tryDragCluster(e: PointerEvent) {
+        let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
+        let cluster = this.childDocs.reduce((cluster, cd) => {
+            let cx = NumCast(cd.x) - this._clusterDistance;
+            let cy = NumCast(cd.y) - this._clusterDistance;
+            let cw = NumCast(cd.width) + 2 * this._clusterDistance;
+            let ch = NumCast(cd.height) + 2 * this._clusterDistance;
+            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+                return NumCast(cd.cluster);
+            return cluster;
+        }, -1);
+        if (cluster !== -1) {
+            let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+            this.selectDocuments(eles);
+            let clusterDocs = SelectionManager.SelectedDocuments();
+            SelectionManager.DeselectAll();
+            let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+            de.moveDocument = this.props.moveDocument;
+            const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
+            const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
+            de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+            de.xOffset = xoff;
+            de.yOffset = yoff;
+            DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
+                handlers: { dragComplete: action(emptyFunction) },
+                hideSource: !de.dropAction
+            });
+            return true;
+        }
+
+        return false;
+    }
+    @observable sets: (Doc[])[] = [];
     @action
     updateClusters() {
-        let sets: (Doc[])[] = []
+        this.sets.length = 0;
         this.childDocs.map(c => {
             let included = []
-            for (let i = 0; i < sets.length; i++) {
-                for (let j = 0; j < sets[i].length; j++) {
-                    if (this.bounsdSelect(c, sets[i][j])) {
+            for (let i = 0; i < this.sets.length; i++) {
+                for (let j = 0; j < this.sets[i].length; j++) {
+                    if (this.bounsdSelect(c, this.sets[i][j])) {
                         included.push(i);
                         break;
                     }
                 }
             }
             if (included.length === 0)
-                sets.push([c]);
+                this.sets.push([c]);
             else if (included.length === 1)
-                sets[included[0]].push(c);
+                this.sets[included[0]].push(c);
             else {
-                sets[included[0]].push(c);
+                this.sets[included[0]].push(c);
                 for (let s = 1; s < included.length; s++) {
-                    sets[included[0]].push(...sets[included[s]]);
-                    sets[included[s]].length = 0;
+                    this.sets[included[0]].push(...this.sets[included[s]]);
+                    this.sets[included[s]].length = 0;
                 }
             }
         });
-        for (let s = 0; s < sets.length; s++) {
-            for (let i = 0; i < sets[s].length; i++) {
-                Doc.GetProto(sets[s][i]).cluster = s;
+        for (let s = 0; s < this.sets.length; s++) {
+            for (let i = 0; i < this.sets[s].length; i++) {
+                this.sets[s][i].cluster = s;
+            }
+        }
+    }
+
+    getClusterColor = (doc: Doc) => {
+        if (this.props.Document.useClusters) {
+            let cluster = NumCast(doc.cluster);
+            let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined;
+            let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+            let clusterColor = colors[cluster % colors.length];
+            for (let i = 0; set && i < set.length; i++) {
+                if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor);
             }
+            return clusterColor;
         }
+        return "";
     }
 
     @action
@@ -249,34 +298,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
-            let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
-            let cluster = this.childDocs.reduce((cluster, cd) => {
-                let cx = NumCast(cd.x) - this._groupingBorder;
-                let cy = NumCast(cd.y) - this._groupingBorder;
-                let cw = NumCast(cd.width) + 2 * this._groupingBorder;
-                let ch = NumCast(cd.height) + 2 * this._groupingBorder;
-                if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
-                    return NumCast(cd.cluster);
-                return cluster;
-            }, -1);
-            if (cluster !== -1) {
-                let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
-                this.selectDocuments(eles);
-                let clusterDocs = SelectionManager.SelectedDocuments();
-                SelectionManager.DeselectAll();
-                let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
-                de.moveDocument = this.props.moveDocument;
-                const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
-                const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
-                de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
-                de.xOffset = xoff;
-                de.yOffset = yoff;
-                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
-                    handlers: {
-                        dragComplete: action(emptyFunction)
-                    },
-                    hideSource: !de.dropAction
-                });
+            if (this.props.Document.useClusters && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
                 document.removeEventListener("pointermove", this.onPointerMove);
@@ -493,6 +515,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
+            backgroundColor: this.getClusterColor,
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
@@ -516,6 +539,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
+            backgroundColor: returnEmptyString,
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
@@ -625,6 +649,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => this.props.Document.fitToBox = !this.fitToBox,
             icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
         });
+        layoutItems.push({
+            description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
+            event: async () => {
+                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb";
+                Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
+                this.props.Document.useClusters = !this.props.Document.useClusters;
+            },
+            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         layoutItems.push({
             description: "Arrange contents in grid",
             icon: "table",
@@ -700,10 +733,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ...this.views
     ]
     private overlayChildViews = () => {
-        console.log(this.overlayViews.length);
-        return [
-            ...this.overlayViews
-        ];
+        return [...this.overlayViews];
     }
 
     public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b9ee588dd..ff96bd993 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -278,6 +278,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 panX: 0,
                 panY: 0,
                 backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
+                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
                 width: bounds.width,
                 height: bounds.height,
                 title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3b6c443c2..ee596c841 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -8,6 +8,7 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"
 import "./DocumentView.scss";
 import React = require("react");
 import { Doc } from "../../../new_fields/Doc";
+import { returnEmptyString } from "../../../Utils";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     x?: number;
@@ -69,6 +70,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
 
+    @computed
+    get clusterColor() { return this.props.backgroundColor(this.props.Document); }
+
+    clusterColorFunc = (doc: Doc) => this.clusterColor;
+
     render() {
         const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
@@ -77,7 +83,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
-                    boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
+                    boxShadow: this.props.Document.z ? `#9c9396  ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
+                        this.clusterColor ? (
+                            this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
+                                `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
@@ -88,6 +97,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 <DocumentView {...this.props}
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
+                    backgroundColor={this.clusterColorFunc}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
                     animateBetweenIcon={this.animateBetweenIcon}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 16ae5471d..ae6a01ab4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -95,6 +95,7 @@ export interface DocumentViewProps {
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
+    backgroundColor: (doc: Doc) => string;
     getScale: () => number;
     animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
     ChromeHeight?: () => number;
@@ -675,12 +676,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
         return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
     }
+
     render() {
-        if (this.Document.hidden) {
-            return null;
-        }
-        let self = this;
-        let backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+        let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
@@ -695,8 +693,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
-        let groupCol = colors[NumCast(this.props.Document.cluster) % colors.length];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -705,9 +701,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: this.layoutDoc.isBackground ?
-                        `0px 0px 50px 50px ${groupCol}` :
-                        `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
@@ -717,7 +710,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
-                    background: this.layoutDoc.isBackground ? groupCol : backgroundColor,
+                    background: backgroundColor,
                     width: nativeWidth,
                     height: nativeHeight,
                     transform: `scale(${this.props.ContentScaling()})`,
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 11f3eb846..e2d8daea9 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils, returnFalse, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { ContextMenu } from "../ContextMenu";
@@ -843,6 +843,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     PanelWidth={() => 350}
                     PanelHeight={() => 90}
                     focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
                     selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0390359b3..1b9bba5c6 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, Utils, returnEmptyString } from "../../../Utils";
 import { DocumentType } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SetupDrag, DragManager } from "../../util/DragManager";
@@ -223,6 +223,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     PanelWidth={returnXDimension}
                     PanelHeight={returnYDimension}
                     focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
                     selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
-- 
cgit v1.2.3-70-g09d2


From 098e3780c9d16738070a92dbfcdf68372a9d18a9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 3 Aug 2019 15:35:51 -0400
Subject: from last

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ee21c5a4d..24439ef4c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -135,7 +135,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
     _clusterDistance = 75;
-    bounsdSelect(doc: Doc, doc2: Doc) {
+    boundsOverlap(doc: Doc, doc2: Doc) {
         var x2 = NumCast(doc2.x) - this._clusterDistance;
         var y2 = NumCast(doc2.y) - this._clusterDistance;
         var w2 = NumCast(doc2.width) + this._clusterDistance;
@@ -239,7 +239,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let included = []
             for (let i = 0; i < this.sets.length; i++) {
                 for (let j = 0; j < this.sets[i].length; j++) {
-                    if (this.bounsdSelect(c, this.sets[i][j])) {
+                    if (this.boundsOverlap(c, this.sets[i][j])) {
                         included.push(i);
                         break;
                     }
-- 
cgit v1.2.3-70-g09d2


From b7c70cf115ea3d23e06fc7a3837e983b62993334 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 3 Aug 2019 17:30:58 -0400
Subject: a few updates and an option to switch between default backgrounds and
 cluster backgrounds.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx    | 20 +++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx              |  4 +++-
 2 files changed, 18 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 24439ef4c..b4d065d26 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,12 +267,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getClusterColor = (doc: Doc) => {
         if (this.props.Document.useClusters) {
             let cluster = NumCast(doc.cluster);
-            let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined;
+            if (this.sets.length <= cluster) {
+                setTimeout(() => this.updateClusters(), 0);
+                return;
+            }
+            let set = this.sets.length > cluster ? this.sets[cluster] : undefined;
             let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
             let clusterColor = colors[cluster % colors.length];
-            for (let i = 0; set && i < set.length; i++) {
-                if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor);
-            }
+            set && set.filter(s => !s.isBackground).map(s =>
+                s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor)));
+            set && set.filter(s => s.isBackground).map(s =>
+                s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor)));
             return clusterColor;
         }
         return "";
@@ -652,12 +657,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         layoutItems.push({
             description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
             event: async () => {
-                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb";
+                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes
                 Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
                 this.props.Document.useClusters = !this.props.Document.useClusters;
             },
             icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
         });
+        layoutItems.push({
+            description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
+            event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
+            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         layoutItems.push({
             description: "Arrange contents in grid",
             icon: "table",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ae6a01ab4..0347fc9b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -678,7 +678,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor);
+        let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ?
+            this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
+            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
-- 
cgit v1.2.3-70-g09d2


From 02346eabdefd428ca23d6a3fbefdcd51ef62b738 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 4 Aug 2019 01:17:29 -0400
Subject: fixed errors/warnings

---
 src/client/apis/youtube/YoutubeBox.tsx             |   8 +-
 src/client/views/InkingControl.tsx                 |   4 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 .../views/collections/CollectionSchemaCells.tsx    |   4 +-
 .../views/collections/CollectionSchemaHeaders.tsx  |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   5 +-
 .../views/collections/CollectionStackingView.tsx   | 116 +++++++++------------
 src/client/views/collections/CollectionView.tsx    |  11 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  23 ++--
 src/client/views/nodes/ButtonBox.tsx               |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |   4 +-
 src/client/views/search/FilterBox.tsx              |   2 +-
 src/client/views/search/SearchItem.tsx             |   2 +-
 src/client/views/search/ToggleBar.tsx              |   2 +-
 src/new_fields/Doc.ts                              |   4 +-
 16 files changed, 82 insertions(+), 111 deletions(-)

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

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index dc142802c..d73988bb8 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -57,7 +57,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
         if (awaitedBackUp) {
 
 
-            let jsonList = await DocListCastAsync(awaitedBackUp!.json);
+            let jsonList = await DocListCastAsync(awaitedBackUp.json);
             let jsonDetailList = await DocListCastAsync(awaitedDetails!.json);
 
             if (jsonList!.length !== 0) {
@@ -76,8 +76,8 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
                     let videoDescription = StrCast(snippet!.description);
                     let pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!;
                     let channelTitle = StrCast(snippet!.channelTitle);
-                    let duration: string;
-                    let viewCount: string;
+                    let duration: string = "";
+                    let viewCount: string = "";
                     if (jsonDetailList!.length !== 0) {
                         let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc);
                         let statistics = await Cast(jsonDetailList![index].statistics, Doc);
@@ -85,7 +85,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
                         viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!;
                     }
                     index = index + 1;
-                    let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! };
+                    let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration, viewCount: viewCount };
                     runInAction(() => this.curVideoTemplates.push(newTemplate));
                 }
             }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 58c83915b..3f40642b5 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
 import { observable, action, computed, runInAction } from "mobx";
-import { ColorState } from 'react-color';
+import { ColorResult } from 'react-color';
 import React = require("react");
 import { observer } from "mobx-react";
 import "./InkingControl.scss";
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
     }
 
     @undoBatch
-    switchColor = action((color: ColorState): void => {
+    switchColor = action((color: ColorResult): void => {
         this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
         if (InkingControl.Instance.selectedTool === InkTool.None) {
             if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 6dd908445..393e97a7e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -68,7 +68,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 }
             }, 10);
         } else if (topDocView.props.ContainingCollectionView) {
-            let collView = topDocView.props.ContainingCollectionView!;
+            let collView = topDocView.props.ContainingCollectionView;
             let [sx, sy] = xf.inverse().transformPoint(0, 0);
             let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
             topDoc.x = x;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 4ff65b277..7e3061354 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -175,11 +175,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         };
         let onPointerEnter = (e: React.PointerEvent): void => {
             if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
-                dragRef!.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
+                dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
             }
         };
         let onPointerLeave = (e: React.PointerEvent): void => {
-            dragRef!.current!.className = "collectionSchemaView-cellContainer";
+            dragRef.current!.className = "collectionSchemaView-cellContainer";
         };
 
         let contents: any = "incorrect type";
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index dfd65770e..d24f63fbb 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -13,7 +13,7 @@ import { faFile } from "@fortawesome/free-regular-svg-icons";
 import { SchemaHeaderField, RandomPastel, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
 import { undoBatch } from "../../util/UndoManager";
 
-library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile, faSortAmountDown, faSortAmountUp, faTimes);
+library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes);
 
 export interface HeaderProps {
     keyValue: SchemaHeaderField;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2d4c88b94..75787c0a8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -303,14 +303,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             return resized;
         }, [] as { "id": string, "value": number }[]);
     }
-
-    @computed get sorted(): { "id": string, "desc": boolean }[] {
+    @computed get sorted(): { "id": string, "desc"?: true }[] {
         return this.columns.reduce((sorted, shf) => {
             if (shf.desc) {
                 sorted.push({ "id": shf.heading, "desc": shf.desc });
             }
             return sorted;
-        }, [] as { "id": string, "desc": boolean }[]);
+        }, [] as { "id": string, "desc"?: true }[]);
     }
 
     @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d7c3ac3b8..4a751c84c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,26 +1,25 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, reaction, untracked, observable, runInAction } from "mobx";
+import { CursorProperty } from "csstype";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc";
+import Switch from 'rc-switch';
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils, returnTrue } from "../../../Utils";
-import { CollectionSchemaPreview } from "./CollectionSchemaView";
-import "./CollectionStackingView.scss";
-import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
-import { undoBatch } from "../../util/UndoManager";
-import { DragManager } from "../../util/DragManager";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction } from "../../../Utils";
 import { DocumentType } from "../../documents/Documents";
+import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
-import { CursorProperty } from "csstype";
-import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
-import { listSpec } from "../../../new_fields/Schema";
-import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField";
-import { List } from "../../../new_fields/List";
+import { undoBatch } from "../../util/UndoManager";
 import { EditableView } from "../EditableView";
-import { CollectionViewProps } from "./CollectionBaseView";
-import Switch from 'rc-switch';
+import { CollectionSchemaPreview } from "./CollectionSchemaView";
+import "./CollectionStackingView.scss";
+import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
+import { CollectionSubView } from "./CollectionSubView";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -32,13 +31,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _columnStart: number = 0;
     @observable private cursor: CursorProperty = "grab";
     get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
-    @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
     @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
     @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
+    @computed get sectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : ""; }
 
     get layoutDoc() {
         // if this document's layout field contains a document (ie, a rendering template), then we will use that
@@ -46,37 +45,32 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
     }
 
-    @computed
-    get SectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : "" }
 
     get Sections() {
-        let sectionHeaders = this.sectionHeaders;
-        if (!sectionHeaders) {
-            this.props.Document.sectionHeaders = sectionHeaders = new List();
+        if (!this.sectionFilter) return new Map<SchemaHeaderField, Doc[]>();
+
+        if (this.sectionHeaders === undefined) {
+            this.props.Document.sectionHeaders = new List<SchemaHeaderField>();
         }
-        let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []]));
-        if (this.SectionFilter) {
-            this.filteredChildren.map(d => {
-                let sectionValue = (d[this.SectionFilter] ? d[this.SectionFilter] : `NO ${this.SectionFilter.toUpperCase()} VALUE`) as object;
-                // the next five lines ensures that floating point rounding errors don't create more than one section -syip
-                let parsed = parseInt(sectionValue.toString());
-                let castedSectionValue: any = sectionValue;
-                if (!isNaN(parsed)) {
-                    castedSectionValue = parsed;
-                }
+        const sectionHeaders = this.sectionHeaders!;
+        let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
+        this.filteredChildren.map(d => {
+            let sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object;
+            // the next five lines ensures that floating point rounding errors don't create more than one section -syip
+            let parsed = parseInt(sectionValue.toString());
+            let castedSectionValue = !isNaN(parsed) ? parsed : sectionValue;
 
-                // look for if header exists already
-                let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`));
-                if (existingHeader) {
-                    fields.get(existingHeader)!.push(d);
-                }
-                else {
-                    let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`);
-                    fields.set(newSchemaHeader, [d]);
-                    sectionHeaders!.push(newSchemaHeader);
-                }
-            });
-        } else fields.clear();
+            // look for if header exists already
+            let existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`));
+            if (existingHeader) {
+                fields.get(existingHeader)!.push(d);
+            }
+            else {
+                let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`);
+                fields.set(newSchemaHeader, [d]);
+                sectionHeaders.push(newSchemaHeader);
+            }
+        });
         return fields;
     }
 
@@ -96,10 +90,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
         // reset section headers when a new filter is inputted
         this._sectionFilterDisposer = reaction(
-            () => this.SectionFilter,
-            () => {
-                this.props.Document.sectionHeaders = new List();
-            }
+            () => this.sectionFilter,
+            () => this.props.Document.sectionHeaders = new List()
         );
     }
     componentWillUnmount() {
@@ -186,8 +178,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        let targInd = -1;
         let where = [de.x, de.y];
+        let targInd = -1;
         if (de.data instanceof DragManager.DocumentDragData) {
             this._docXfs.map((cd, i) => {
                 let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
@@ -233,8 +225,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             }
         });
     }
+    headings = () => Array.from(this.Sections.keys());
     section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
-        let key = this.SectionFilter;
+        let key = this.sectionFilter;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
         let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
@@ -245,7 +238,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return <CollectionStackingViewFieldColumn
             key={heading ? heading.heading : ""}
             cols={cols}
-            headings={() => Array.from(this.Sections.keys())}
+            headings={this.headings}
             heading={heading ? heading.heading : ""}
             headingObject={heading}
             docList={docList}
@@ -254,16 +247,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             createDropTarget={this.createDropTarget}
             screenToLocalTransform={this.props.ScreenToLocalTransform}
         />;
-
     }
 
     @action
     addGroup = (value: string) => {
-        if (value) {
-            if (this.sectionHeaders) {
-                this.sectionHeaders.push(new SchemaHeaderField(value));
-                return true;
-            }
+        if (value && this.sectionHeaders) {
+            this.sectionHeaders.push(new SchemaHeaderField(value));
+            return true;
         }
         return false;
     }
@@ -276,11 +266,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     onToggle = (checked: Boolean) => {
-        if (checked) {
-            this.props.CollectionView.props.Document.chromeStatus = 'collapsed';
-        } else {
-            this.props.CollectionView.props.Document.chromeStatus = 'view-mode';
-        }
+        this.props.CollectionView.props.Document.chromeSatus = checked ? "collapsed" : "view-mode";
     }
 
     render() {
@@ -296,10 +282,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return (
             <div className="collectionStackingView"
                 ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
-                {this.SectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
-                    map(section => this.section(section[0], section[1])) :
+                {this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
+                    map((section: [SchemaHeaderField, Doc[]]) => this.section(section[0], section[1])) :
                     this.section(undefined, this.filteredChildren)}
-                {(this.SectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
+                {(this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
                     <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
                         style={{ width: (this.columnWidth / (headings.length + ((this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0))) - 10, marginTop: 10 }}>
                         <EditableView {...editableViewProps} />
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 57dc5879b..f59fee985 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -20,16 +20,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 export const COLLECTION_BORDER_WIDTH = 2;
 
-library.add(faTh);
-library.add(faTree);
-library.add(faSquare);
-library.add(faProjectDiagram);
-library.add(faSignature);
-library.add(faThList);
-library.add(faFingerprint);
-library.add(faColumns);
-library.add(faEllipsisV);
-library.add(faImage, faEye);
+library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any);
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b4d065d26..c943fac74 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -40,7 +40,7 @@ import v5 = require("uuid/v5");
 import { setScheduler } from "bluebird";
 import { DocumentType, Docs } from "../../../documents/Documents";
 
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
+library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -206,8 +206,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let cy = NumCast(cd.y) - this._clusterDistance;
             let cw = NumCast(cd.width) + 2 * this._clusterDistance;
             let ch = NumCast(cd.height) + 2 * this._clusterDistance;
-            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) {
                 return NumCast(cd.cluster);
+            }
             return cluster;
         }, -1);
         if (cluster !== -1) {
@@ -236,20 +237,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     updateClusters() {
         this.sets.length = 0;
         this.childDocs.map(c => {
-            let included = []
+            let included = [];
             for (let i = 0; i < this.sets.length; i++) {
-                for (let j = 0; j < this.sets[i].length; j++) {
-                    if (this.boundsOverlap(c, this.sets[i][j])) {
+                for (let member of this.sets[i]) {
+                    if (this.boundsOverlap(c, member)) {
                         included.push(i);
                         break;
                     }
                 }
             }
-            if (included.length === 0)
+            if (included.length === 0) {
                 this.sets.push([c]);
-            else if (included.length === 1)
+            } else if (included.length === 1) {
                 this.sets[included[0]].push(c);
-            else {
+            } else {
                 this.sets[included[0]].push(c);
                 for (let s = 1; s < included.length; s++) {
                     this.sets[included[0]].push(...this.sets[included[s]]);
@@ -257,11 +258,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
             }
         });
-        for (let s = 0; s < this.sets.length; s++) {
-            for (let i = 0; i < this.sets[s].length; i++) {
-                this.sets[s][i].cluster = s;
-            }
-        }
+        this.sets.map((set, i) => set.map(member => member.cluster = i));
     }
 
     getClusterColor = (doc: Doc) => {
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index e2c559c9a..640795789 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -16,7 +16,7 @@ import './ButtonBox.scss';
 import { observer } from 'mobx-react';
 import { DocumentIconContainer } from './DocumentIcon';
 
-library.add(faEdit);
+library.add(faEdit as any);
 
 const ButtonSchema = createSchema({
     onClick: ScriptField,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0347fc9b2..4528cf94d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -95,7 +95,7 @@ export interface DocumentViewProps {
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
-    backgroundColor: (doc: Doc) => string;
+    backgroundColor: (doc: Doc) => string | undefined;
     getScale: () => number;
     animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
     ChromeHeight?: () => number;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fa072aecf..a49709e83 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -152,9 +152,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
 
     scrollTo(y: number) {
-        if (this._mainCont.current) {
-            this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current!.offsetHeight / 2), 0), behavior: "auto" });
-        }
+        this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
     }
 
     settingsPanel() {
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 995ddd5c3..3e8582d61 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -384,7 +384,7 @@ export class FilterBox extends React.Component {
                     <div className="active-icon description">Collection Filters Active</div>
                 </div> : undefined}
             </div>
-        )
+        );
     }
 
     // Useful queries:
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 1b9bba5c6..48eb87251 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -134,7 +134,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {
                 <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div>
                 <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div>
             </div>
-        )
+        );
     }
 
 }
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index a30104089..ed5ecd3ba 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -59,7 +59,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
         this._forwardTimeline.play();
         this._forwardTimeline.reverse();
         this.props.handleChange();
-        console.log(this.props.getStatus())
+        console.log(this.props.getStatus());
     }
 
     @action.bound
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b081588a3..c01f4e8cf 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -358,7 +358,7 @@ export namespace Doc {
         let docExtensionForField = doc[fieldKey + "_ext"] as Doc;
         if (docExtensionForField === undefined) {
             setTimeout(() => {
-                CreateDocumentExtensionForField(doc, field);
+                CreateDocumentExtensionForField(doc, fieldKey);
             }, 0);
         } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already
             docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0);
@@ -535,7 +535,7 @@ export namespace Doc {
                 d.layout = detailLayout;
                 d.nativeWidth = d.nativeHeight = undefined;
                 if (detailLayout instanceof Doc) {
-                    let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc;
+                    let delegDetailLayout = Doc.MakeDelegate(detailLayout);
                     d.layout = delegDetailLayout;
                     delegDetailLayout.layout = await delegDetailLayout.detailedLayout;
                 }
-- 
cgit v1.2.3-70-g09d2


From a8aa834d8e08bddde705ce90ef4dee8be3791862 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 4 Aug 2019 21:56:11 -0400
Subject: fixed dropping of PDFs (or anything that doesn't have a height).

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c943fac74..2bdbb6749 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -69,7 +69,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
         let bounds = boundsList.reduce((bounds, b) => {
             var [sptX, sptY] = [b.x, b.y];
-            let [bptX, bptY] = [sptX + b.width, sptY + b.height];
+            let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)];
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
@@ -617,7 +617,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         ele: <CollectionFreeFormDocumentView key={doc[Id]}
                             x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                             width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
-                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined
+                        bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
                     });
                 }
             }
-- 
cgit v1.2.3-70-g09d2


From 298d1c9b29d6ce2171fd9ac8274b64583b73f6f5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 5 Aug 2019 18:32:53 -0400
Subject: added reset view menu option

---
 .../collectionFreeForm/CollectionFreeFormView.tsx        | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2bdbb6749..a08a12426 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
@@ -36,11 +36,9 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import v5 = require("uuid/v5");
-import { setScheduler } from "bluebird";
 import { DocumentType, Docs } from "../../../documents/Documents";
 
-library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
+library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -651,6 +649,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => this.props.Document.fitToBox = !this.fitToBox,
             icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
         });
+        layoutItems.push({
+            description: "reset view", event: () => {
+                this.props.Document.panX = this.props.Document.panY = 0;
+                this.props.Document.scale = 1;
+            }, icon: "compress-arrows-alt"
+        });
         layoutItems.push({
             description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
             event: async () => {
@@ -658,12 +662,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
                 this.props.Document.useClusters = !this.props.Document.useClusters;
             },
-            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+            icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
         layoutItems.push({
             description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
             event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
-            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+            icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
         });
         layoutItems.push({
             description: "Arrange contents in grid",
-- 
cgit v1.2.3-70-g09d2

