From 173863d85ee590c276bf22b1cfe91e0d00986720 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Sep 2019 13:45:04 -0400
Subject: added named target docs from rich text.

---
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 27eafd769..5015ee39a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -93,9 +93,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
             });
         } else if (!e.ctrlKey) {
-            let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
-            newBox.proto!.autoHeight = true;
-            this.props.addLiveTextDocument(newBox);
+            this.props.addLiveTextDocument(
+                Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
         }
         e.stopPropagation();
     }
-- 
cgit v1.2.3-70-g09d2


From 2d21fff15510d6eeb8975cc2459f69ca28d86d1d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 9 Sep 2019 17:26:27 -0400
Subject: added stand-in spatial parser.

---
 src/client/util/TooltipTextMenu.tsx                         | 11 +++++++++++
 src/client/views/DocumentDecorations.tsx                    |  6 ++++++
 src/client/views/InkingControl.tsx                          |  7 ++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx           | 13 +++++++++++++
 .../views/collections/collectionFreeForm/MarqueeView.tsx    |  1 +
 src/client/views/nodes/FormattedTextBox.tsx                 | 10 ++++++++++
 6 files changed, 47 insertions(+), 1 deletion(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 020c51c36..c376b6f86 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -18,6 +18,7 @@ import { DragManager } from "./DragManager";
 import { LinkManager } from "./LinkManager";
 import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
+import { Cast, NumCast } from '../../new_fields/Types';
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
@@ -495,10 +496,20 @@ export class TooltipTextMenu {
             if (markType.name[0] === 'p') {
                 let size = this.fontSizeToNum.get(markType);
                 if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(this.editorProps.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleSize_" + heading] = size;
+                }
             }
             else {
                 let fontName = this.fontStylesToName.get(markType);
                 if (fontName) { this.updateFontStyleDropdown(fontName); }
+                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(this.editorProps.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleFont_" + heading] = fontName;
+                }
             }
             //actually apply font
             return toggleMark(markType)(view.state, view.dispatch, view);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 700a4b49d..7cdb16f52 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -428,6 +428,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
         SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
             map(d => d.borderRounding = `${Math.min(100, dist)}%`);
+        SelectionManager.SelectedDocuments().map(dv => {
+            let cv = dv.props.ContainingCollectionView;
+            let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
+            let heading = NumCast(dv.props.Document.heading);
+            cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding));
+        })
         e.stopPropagation();
         e.preventDefault();
     }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 3f40642b5..eb6312e78 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -9,7 +9,7 @@ import { SelectionManager } from "../util/SelectionManager";
 import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { undoBatch, UndoManager } from "../util/UndoManager";
-import { StrCast } from "../../new_fields/Types";
+import { StrCast, NumCast, Cast } from "../../new_fields/Types";
 import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { MainOverlayTextBox } from "./MainOverlayTextBox";
 
@@ -50,6 +50,11 @@ export class InkingControl extends React.Component {
                 let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
                 let oldColor = StrCast(targetDoc.backgroundColor);
                 targetDoc.backgroundColor = this._selectedColor;
+                if (view.props.Document.heading) {
+                    let cv = view.props.ContainingCollectionView;
+                    let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
+                    cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor);
+                }
                 return {
                     target: targetDoc,
                     previous: oldColor
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2ec2b0671..f7c1bedbb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -256,6 +256,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+        newBox.heading = 1;
+        for (let i = 0; i < this.childDocs.length; i++) {
+            if (this.childDocs[i].heading == 1) {
+                newBox.heading = 2;
+            }
+        }
+        let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
+        if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document;
+        let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
+        let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
+        round && (newBox.borderRounding = round);
+        col && (newBox.backgroundColor = col);
+        newBox.ruleProvider = ruleProvider;
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5015ee39a..100e6d817 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -329,6 +329,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
+                newCollection.ruleProvider = this.props.container.props.Document;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ae1393fc4..93c97fa23 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -665,6 +665,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         else if (this.props.isOverlay) this._editorView!.focus();
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
         this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
+        let heading = this.props.Document.heading;
+        if (heading) {
+            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
+            if (ruleProvider instanceof Doc) {
+                let font = StrCast(ruleProvider["ruleFont_" + heading]);
+                let size = NumCast(ruleProvider["ruleSize_" + heading]);
+                size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]);
+                font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]);
+            }
+        }
     }
 
     componentWillUnmount() {
-- 
cgit v1.2.3-70-g09d2


From 5ea7e3318620865146318e0f3826b6f13aec0675 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 10 Sep 2019 13:38:16 -0400
Subject: added more support for creating stylized layouts

---
 src/Utils.ts                                       | 92 ++++++++++++++++++++++
 src/client/util/ProsemirrorExampleTransfer.ts      |  5 --
 src/client/util/RichTextRules.ts                   | 40 ++++++++++
 src/client/util/RichTextSchema.tsx                 | 19 +++++
 src/client/views/ContextMenu.tsx                   |  1 +
 src/client/views/DocumentDecorations.tsx           |  5 ++
 src/client/views/InkingControl.tsx                 | 32 +++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 ++++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++++-
 src/client/views/nodes/FormattedTextBox.tsx        | 48 ++++++++---
 .../authentication/models/current_user_utils.ts    | 20 ++---
 11 files changed, 270 insertions(+), 45 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index f805ae872..3921a49c3 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -53,6 +53,98 @@ export class Utils {
         document.body.removeChild(textArea);
     }
 
+    public static fromRGBAstr(rgba: string) {
+        let rm = rgba.match(/rgb[a]?\(([0-9]+)/);
+        let r = rm ? Number(rm[1]) : 0;
+        let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/);
+        let g = gm ? Number(gm[1]) : 0;
+        let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/);
+        let b = bm ? Number(bm[1]) : 0;
+        let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/);
+        let a = am ? Number(am[1]) : 0;
+        return { r: r, g: g, b: b, a: a };
+    }
+    public static toRGBAstr(col: { r: number, g: number, b: number, a?: number }) {
+        return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")";
+    }
+
+    public static HSLtoRGB(h: number, s: number, l: number) {
+        // Must be fractions of 1
+        // s /= 100;
+        // l /= 100;
+
+        let c = (1 - Math.abs(2 * l - 1)) * s,
+            x = c * (1 - Math.abs((h / 60) % 2 - 1)),
+            m = l - c / 2,
+            r = 0,
+            g = 0,
+            b = 0;
+        if (0 <= h && h < 60) {
+            r = c; g = x; b = 0;
+        } else if (60 <= h && h < 120) {
+            r = x; g = c; b = 0;
+        } else if (120 <= h && h < 180) {
+            r = 0; g = c; b = x;
+        } else if (180 <= h && h < 240) {
+            r = 0; g = x; b = c;
+        } else if (240 <= h && h < 300) {
+            r = x; g = 0; b = c;
+        } else if (300 <= h && h < 360) {
+            r = c; g = 0; b = x;
+        }
+        r = Math.round((r + m) * 255);
+        g = Math.round((g + m) * 255);
+        b = Math.round((b + m) * 255);
+        return { r: r, g: g, b: b };
+    }
+
+    public static RGBToHSL(r: number, g: number, b: number) {
+        // Make r, g, and b fractions of 1
+        r /= 255;
+        g /= 255;
+        b /= 255;
+
+        // Find greatest and smallest channel values
+        let cmin = Math.min(r, g, b),
+            cmax = Math.max(r, g, b),
+            delta = cmax - cmin,
+            h = 0,
+            s = 0,
+            l = 0;
+        // Calculate hue
+
+        // No difference
+        if (delta == 0)
+            h = 0;
+        // Red is max
+        else if (cmax == r)
+            h = ((g - b) / delta) % 6;
+        // Green is max
+        else if (cmax == g)
+            h = (b - r) / delta + 2;
+        // Blue is max
+        else
+            h = (r - g) / delta + 4;
+
+        h = Math.round(h * 60);
+
+        // Make negative hues positive behind 360°
+        if (h < 0)
+            h += 360; // Calculate lightness
+
+        l = (cmax + cmin) / 2;
+
+        // Calculate saturation
+        s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
+
+        // Multiply l and s by 100
+        // s = +(s * 100).toFixed(1);
+        // l = +(l * 100).toFixed(1);
+
+        return { h: h, s: s, l: l };
+    }
+
+
     public static GetClipboardText(): string {
         var textArea = document.createElement("textarea");
         document.body.appendChild(textArea);
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index bac0177ad..1d2d33800 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -14,7 +14,6 @@ export type KeyMap = { [key: string]: any };
 export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
     let keys: { [key: string]: any } = {}, type;
 
-    keys["ACTIVE"] = false;
     function bind(key: string, cmd: any) {
         if (mapKeys) {
             let mapped = mapKeys[key];
@@ -148,10 +147,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         return tx;
     }
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu
-            dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection());
-            return true;
-        }
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) {
             if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 5d1131410..00e671db9 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -2,6 +2,9 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from
 import { schema } from "./RichTextSchema";
 import { wrappingInputRule } from "./prosemirrorPatches";
 import { NodeSelection } from "prosemirror-state";
+import { NumCast, Cast } from "../../new_fields/Types";
+import { Doc } from "../../new_fields/Doc";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 export const inpRules = {
     rules: [
@@ -57,6 +60,12 @@ export const inpRules = {
         new InputRule(
             new RegExp(/^#([0-9]+)\s$/),
             (state, match, start, end) => {
+                let size = Number(match[1]);
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleSize_" + heading] = size;
+                }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }))
             }),
         new InputRule(
@@ -64,9 +73,40 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleAlign_" + heading] = "center";
+                }
                 return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
             }),
+        new InputRule(
+            new RegExp(/^\[\[\s$/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleAlign_" + heading] = "left";
+                }
+                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+            }),
+        new InputRule(
+            new RegExp(/^\]\]\s$/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleAlign_" + heading] = "right";
+                }
+                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+            }),
         new InputRule(
             new RegExp(/\^f\s$/),
             (state, match, start, end) => {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3def4a579..f027a4bf7 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -12,6 +12,7 @@ import { DocServer } from "../DocServer";
 import { Cast, NumCast } from "../../new_fields/Types";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
+import { times } from "async";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -422,6 +423,24 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() { return codeDOM; }
     },
 
+    // pFontFamily: {
+    //     attrs: {
+    //         style: { default: 'font-family: "Times New Roman", Times, serif;' },
+    //     },
+    //     parseDOM: [{
+    //         tag: "span", getAttrs(dom: any) {
+    //             if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` };
+    //             if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` };
+    //             if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` };
+    //             if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` };
+    //             if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` };
+    //         }
+    //     }],
+    //     toDOM: (node: any) => ['span', {
+    //         style: node.attrs.style
+    //     }]
+    // },
+
 
     /* FONTS */
     timesNewRoman: {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 890bfdfb7..68b97f2b6 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -250,6 +250,7 @@ export class ContextMenu extends React.Component {
             const item = this.flatItems[this.selectedIndex];
             item && item.event();
             this.closeMenu();
+            e.preventDefault();
         }
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7cdb16f52..94aab8b2f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -621,6 +621,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
                 let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
                 let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight);
+                if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) {
+                    doc.ignoreAspect = false;
+                    proto.nativeWidth = nwidth = doc.width || 0;
+                    proto.nativeHeight = nheight = doc.height || 0;
+                }
                 if (fixedAspect && (!nwidth || !nheight)) {
                     proto.nativeWidth = nwidth = doc.width || 0;
                     proto.nativeHeight = nheight = doc.height || 0;
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index eb6312e78..519792308 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -10,8 +10,10 @@ import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { StrCast, NumCast, Cast } from "../../new_fields/Types";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { MainOverlayTextBox } from "./MainOverlayTextBox";
+import { listSpec } from "../../new_fields/Schema";
+import { List } from "../../new_fields/List";
+import { Utils } from "../../Utils";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -49,11 +51,34 @@ export class InkingControl extends React.Component {
             let oldColors = selected.map(view => {
                 let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
                 let oldColor = StrCast(targetDoc.backgroundColor);
-                targetDoc.backgroundColor = this._selectedColor;
+                if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) {
+                    let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[];
+                    let closest = 0;
+                    let dist = 10000000;
+                    let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
+                    for (let i = 0; i < cp.length; i++) {
+                        let cpcol = Utils.fromRGBAstr(cp[i]);
+                        let d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g));
+                        if (d < dist) {
+                            dist = d;
+                            closest = i;
+                        }
+                    }
+                    cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")";
+                    view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp);
+                    targetDoc.backgroundColor = cp[closest];
+                } else
+                    targetDoc.backgroundColor = this._selectedColor;
                 if (view.props.Document.heading) {
                     let cv = view.props.ContainingCollectionView;
                     let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
-                    cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor);
+                    let parback = cv && StrCast(cv.props.Document.backgroundColor);
+                    cv && parback && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
+                    // if (parback && cv && parback.indexOf("rgb") !== -1) {
+                    //     let parcol = Utils.fromRGBAstr(parback);
+                    //     let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b);
+                    //     cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = color.hsl.s - hsl.s);
+                    // }
                 }
                 return {
                     target: targetDoc,
@@ -67,7 +92,6 @@ export class InkingControl extends React.Component {
             });
         }
     });
-
     @action
     switchWidth = (width: string): void => {
         this._selectedWidth = width;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f7c1bedbb..c9b2c30b1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -8,7 +8,7 @@ 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 { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { Docs } from "../../../documents/Documents";
@@ -262,14 +262,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 newBox.heading = 2;
             }
         }
-        let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
-        if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document;
-        let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
-        let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
-        round && (newBox.borderRounding = round);
-        col && (newBox.backgroundColor = col);
-        newBox.ruleProvider = ruleProvider;
-        this.addDocument(newBox, false);
+        PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => {
+            if (!ruleProvider) ruleProvider = this.props.Document;
+            // saturation shift
+            // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
+            // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor));
+            // let hsl = Utils.RGBToHSL(back.r, back.g, back.b);
+            // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l };
+            // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l)));
+            // OR transparency set
+            let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
+            col && (Doc.GetProto(newBox).backgroundColor = col);
+
+            let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
+            round && (newBox.borderRounding = round);
+            newBox.ruleProvider = ruleProvider;
+            this.addDocument(newBox, false);
+        });
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 100e6d817..56d8127e2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,11 +1,11 @@
 import * as htmlToImage from "html-to-image";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, FieldResult } from "../../../../new_fields/Doc";
+import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
-import { Cast, NumCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
 import { Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -20,6 +20,8 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
 import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField";
+import { string } from "prop-types";
+import { listSpec } from "../../../../new_fields/Schema";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -272,14 +274,30 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     return d;
                 });
             }
+            let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+                "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+            let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string"));
+            if (!colorPalette) this.props.container.props.Document.colorPalette = new List<string>(defaultPalette);
+            let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]);
+            let usedPaletted = new Map<string, number>();
+            [...this.props.activeDocuments(), this.props.container.props.Document].map(child => {
+                let bg = StrCast(child.backgroundColor);
+                if (palette.indexOf(bg) !== -1) {
+                    palette.splice(palette.indexOf(bg), 1);
+                    if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
+                    else usedPaletted.set(bg, 1);
+                }
+            });
+            let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
+            let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0];
             let inkData = this.ink ? this.ink.inkData : undefined;
             let newCollection = Docs.Create.FreeformDocument(selected, {
                 x: bounds.left,
                 y: bounds.top,
                 panX: 0,
                 panY: 0,
-                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
-                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
+                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
+                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
                 width: bounds.width,
                 height: bounds.height,
                 title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6a6000dc5..0ea36cdc2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
 import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
 import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
-import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { Utils, numberRange, timenow } from '../../../Utils';
 import { DocServer } from "../../DocServer";
@@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
 import { inputRules } from 'prosemirror-inputrules';
+import { select } from 'async';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -203,8 +204,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
 
-            this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu
-
             this._applyingChange = true;
             this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
             this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
@@ -353,7 +352,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     _keymap: any = undefined;
     @computed get config() {
         this._keymap = buildKeymap(schema);
-        this._keymap["ACTIVE"] = this.extensionDoc.text;  // hack to ignore an initial carriage return only when creating a textbox from the action menu
         return {
             schema,
             plugins: this.props.isOverlay ? [
@@ -659,7 +657,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
 
-        if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) {
+        let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
+        if (selectOnLoad) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
         }
@@ -667,15 +666,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
         this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
         let heading = this.props.Document.heading;
-        if (heading) {
-            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
-            if (ruleProvider instanceof Doc) {
-                let font = StrCast(ruleProvider["ruleFont_" + heading]);
-                let size = NumCast(ruleProvider["ruleSize_" + heading]);
-                size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]);
-                font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]);
-            }
+        if (heading && selectOnLoad) {
+            PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => {
+                if (ruleProvider) {
+                    let align = StrCast(ruleProvider["ruleAlign_" + heading]);
+                    let font = StrCast(ruleProvider["ruleFont_" + heading]);
+                    let size = NumCast(ruleProvider["ruleSize_" + heading]);
+                    if (align) {
+                        let tr = this._editorView!.state.tr;
+                        tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))).
+                            replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: align }), true).
+                            setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0)));
+                        this._editorView!.dispatch(tr);
+                    }
+                    let sm = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
+                    size && (sm = [...sm, schema.marks.pFontSize.create({ fontSize: size })]);
+                    font && (sm = [...sm, this.getFont(font)]);
+                    this._editorView!.dispatch(this._editorView!.state.tr.setStoredMarks(sm));
+                }
+            });
+        }
+    }
+    getFont(font: string) {
+        switch (font) {
+            case "Arial": return schema.marks.arial.create();
+            case "Times New Roman": return schema.marks.timesNewRoman.create();
+            case "Georgia": return schema.marks.georgia.create();
+            case "Comic Sans MS": return schema.marks.comicSans.create();
+            case "Tahoma": return schema.marks.tahoma.create();
+            case "Impact": return schema.marks.impact.create();
+            case "ACrimson Textrial": return schema.marks.crimson.create();
         }
+        return schema.marks.arial.create();
     }
 
     componentWillUnmount() {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9866e22eb..9d35d36d3 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -115,17 +115,17 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
         });
-        try {
-            const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
-            NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
-            await Gateway.Instance.ClearCatalog();
-            const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
-            let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
-            let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
-            // if (catprom) await Promise.all(catprom);
-        } catch (e) {
+        // try {
+        //     const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
+        //     NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
+        //     await Gateway.Instance.ClearCatalog();
+        //     const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+        //     let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
+        //     let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
+        //     // if (catprom) await Promise.all(catprom);
+        // } catch (e) {
 
-        }
+        // }
     }
 
     /* Northstar catalog ... really just for testing so this should eventually go away */
-- 
cgit v1.2.3-70-g09d2


From a709e21384cef80a85eac9220739c854a96d5313 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Sep 2019 20:15:40 -0400
Subject: fixed several search issues with text boxes and highlighting.

---
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 +-
 src/client/views/nodes/DocumentView.tsx            | 16 ++++---
 src/client/views/search/SearchBox.tsx              |  6 +--
 src/client/views/search/SearchItem.tsx             | 56 ++--------------------
 src/server/Search.ts                               |  3 ++
 src/server/index.ts                                |  3 ++
 6 files changed, 27 insertions(+), 62 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 56d8127e2..0c4860be1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -288,8 +288,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     else usedPaletted.set(bg, 1);
                 }
             });
+            usedPaletted.delete("#f1efeb");
+            usedPaletted.delete("white");
+            usedPaletted.delete("rgba(255,255,255,1)");
             let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
-            let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0];
+            let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
             let inkData = this.ink ? this.ink.inkData : undefined;
             let newCollection = Docs.Create.FreeformDocument(selected, {
                 x: bounds.left,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 633455f63..1e755f121 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -644,12 +644,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         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: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) });
         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();
+            description: "Download document", icon: "download", event: async () => {
+                let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
+                    qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
+                }));
+                console.log(y);
+                // 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();
             }
         });
 
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 2ad69daca..2e29838e6 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -141,7 +141,7 @@ export class SearchBox extends React.Component {
     private get filterQuery() {
         const types = FilterBox.Instance.filterTypes;
         const includeDeleted = FilterBox.Instance.getDataStatus();
-        return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
+        return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
     }
 
 
@@ -304,14 +304,14 @@ export class SearchBox extends React.Component {
                         this.getResults(this._searchString);
                         if (i < this._results.length) result = this._results[i];
                         if (result) {
-                            this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />;
+                            this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} highlighting={result[1]} />;
                             this._isSearch[i] = "search";
                         }
                     }
                     else {
                         result = this._results[i];
                         if (result) {
-                            this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />;
+                            this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} highlighting={result[1]} />;
                             this._isSearch[i] = "search";
                         }
                     }
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 386b5fe74..0b722c086 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -28,7 +28,7 @@ import "./SelectorContextMenu.scss";
 
 export interface SearchItemProps {
     doc: Doc;
-    query?: string;
+    query: string;
     highlighting: string[];
 }
 
@@ -128,68 +128,26 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {
 export class SearchItem extends React.Component<SearchItemProps> {
 
     @observable _selected: boolean = false;
-    private _previewDoc?: Doc;
 
     onClick = () => {
         // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like
         DocumentManager.Instance.jumpToDocument(this.props.doc, false);
-        if (this.props.doc.data instanceof RichTextField) {
-            this.highlightTextBox(this.props.doc);
-        }
-        // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
     }
     @observable _useIcons = true;
     @observable _displayDim = 50;
 
-    highlightTextBox = (doc: Doc) => {
-        if (this.props.query) {
-            const fieldkey = 'search_string';
-            if (Object.keys(doc).indexOf(fieldkey) === -1) {
-                doc.search_string = this.props.query;
-            }
-            else {
-                doc.search_string = undefined;
-            }
-
-        }
-    }
-
-    fitToBox = () => {
-        let bounds = Doc.ComputeContentBounds([this.props.doc]);
-        return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim];
-    }
-
     componentWillUnmount() {
-        if (this._previewDoc) {
-            DocServer.DeleteDocument(this._previewDoc[Id]);
-        }
+        this.props.doc.search_string = undefined;
     }
 
-
     //@computed
     @action
     public DocumentIcon() {
         let layoutresult = StrCast(this.props.doc.type);
         if (!this._useIcons) {
-            let renderDoc = this.props.doc;
-            //let box: number[] = [];
-            if (layoutresult.indexOf(DocumentType.COL) !== -1) {
-                renderDoc = Doc.MakeDelegate(renderDoc);
-                let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
-                    var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
-                    let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
-                    return {
-                        x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
-                        r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
-                    };
-                }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
-                let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim];
-            }
             let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
             let returnYDimension = () => this._displayDim;
-            let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
-            let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
-            this._previewDoc = newRenderDoc;
+            let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension());
             const docview = <div
                 onPointerDown={action(() => {
                     this._useIcons = !this._useIcons;
@@ -219,15 +177,9 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     ContentScaling={scale}
                 />
             </div>;
-            const data = renderDoc.data;
-            if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
-            newRenderDoc.preview = true;
-            newRenderDoc.search_string = this.props.query;
+            this.props.doc.search_string = this.props.query;
             return docview;
         }
-        if (this._previewDoc) {
-            DocServer.DeleteDocument(this._previewDoc[Id]);
-        }
         let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
             layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
                 layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 723dc101b..4a408405a 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -32,10 +32,13 @@ export class Search {
 
     public async search(query: any) {
         try {
+            console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q);
+            console.log(query);
             const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
                 qs: query
             }));
             const { docs, numFound } = searchResults.response;
+            console.log("RESULTS " + numFound);
             const ids = docs.map((field: any) => field.id);
             return { ids, numFound, highlighting: searchResults.highlighting };
         } catch {
diff --git a/src/server/index.ts b/src/server/index.ts
index 50ce2b14e..149bacf0f 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -198,12 +198,15 @@ const solrURL = "http://localhost:8983/solr/#/dash";
 
 app.get("/search", async (req, res) => {
     const solrQuery: any = {};
+    console.log("GOT SEARCH");
     ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]);
     if (solrQuery.q === undefined) {
         res.send([]);
         return;
     }
+    console.log("CALLING SEARCH")
     let results = await Search.Instance.search(solrQuery);
+    console.log("RETURNING SEARCH")
     res.send(results);
 });
 
-- 
cgit v1.2.3-70-g09d2


From 68e554cafb6107bfde9526773b3e0e667d582c88 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 11 Sep 2019 17:24:48 -0400
Subject: added default note type collection

---
 src/client/documents/Documents.ts                        |  1 +
 src/client/views/Main.tsx                                |  1 -
 src/client/views/PreviewCursor.tsx                       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx        | 16 +++++++++-------
 .../views/collections/collectionFreeForm/MarqueeView.tsx |  6 ++++++
 src/client/views/nodes/FormattedTextBox.tsx              |  3 ++-
 src/server/authentication/models/current_user_utils.ts   | 12 +++++++++++-
 7 files changed, 30 insertions(+), 11 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ae65fde1e..602a7f9ad 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -66,6 +66,7 @@ export interface DocumentOptions {
     page?: number;
     scale?: number;
     layout?: string;
+    isTemplate?: boolean;
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 0e687737d..11ec6f0c9 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -37,7 +37,6 @@ let swapDocs = async () => {
     (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled";
     (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled";
     (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled";
-    CurrentUserUtils.UserDocument.chromeStatus = "disabled";
     await swapDocs();
     document.getElementById('root')!.addEventListener('wheel', event => {
         if (event.ctrlKey) {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 45a8556bf..1aed51e64 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -102,7 +102,7 @@ export class PreviewCursor extends React.Component<{}> {
             (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
             !e.key.startsWith("Arrow") &&
             !e.defaultPrevented) {
-            if (!e.ctrlKey && !e.metaKey) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
+            if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
                 PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
                 PreviewCursor.Visible = false;
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 21f119d57..2591bdd8d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -39,6 +39,7 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { DocServer } from "../../../DocServer";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -272,7 +273,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l)));
             // OR transparency set
             let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
-            col && (Doc.GetProto(newBox).backgroundColor = col);
+            (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col);
 
             let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
             round && (Doc.GetProto(newBox).borderRounding = round);
@@ -945,17 +946,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
 
         let noteItems: ContextMenuProps[] = [];
-        noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" });
-        noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" });
-        noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" });
-        noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" });
+        let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+        notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" }));
         layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" })
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
-    createText = (noteStyle: string, color: string) => {
+    createText = (noteStyle: number) => {
         let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY);
-        this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color }))
+        let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+        let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) });
+        text.layout = notes[noteStyle % notes.length];
+        this.addLiveTextBox(text);
     }
 
     private childViews = () => [
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0c4860be1..fe48a3485 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -22,6 +22,7 @@ import React = require("react");
 import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField";
 import { string } from "prop-types";
 import { listSpec } from "../../../../new_fields/Schema";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -97,6 +98,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         } else if (!e.ctrlKey) {
             this.props.addLiveTextDocument(
                 Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
+        } else if (e.keyCode > 48 && e.keyCode <= 57) {
+            let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+            let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
+            text.layout = notes[(e.keyCode - 49) % notes.length];
+            this.props.addLiveTextDocument(text);
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0ea36cdc2..194026a08 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -289,7 +289,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") {
-                this.props.Document.layout = draggedDoc;
+                if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc;
+                else this.props.Document.layout = draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
             }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9d35d36d3..af5774ebe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -7,7 +7,7 @@ import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/nort
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
 import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
 import { CollectionView } from "../../../client/views/collections/CollectionView";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types";
@@ -50,6 +50,16 @@ export class CurrentUserUtils {
             doc.workspaces = workspaces;
         }
         PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true));
+        if (doc.noteTypes === undefined) {
+            let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })];
+            const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 });
+            noteTypes.excludeFromLibrary = true;
+            doc.noteTypes = noteTypes;
+        }
+        PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(vals => DocListCast(vals)));
         if (doc.recentlyClosed === undefined) {
             const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 });
             recentlyClosed.excludeFromLibrary = true;
-- 
cgit v1.2.3-70-g09d2


From 47ca25c49d7d9f1fee22b256f86e296dac42b47b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 12 Sep 2019 21:42:07 -0400
Subject: cleaned up rulerProvider a bit and added menu item to turn it on.

---
 src/client/views/DocumentDecorations.tsx           | 13 +++++----
 src/client/views/InkingControl.tsx                 |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 31 +++++++---------------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +++
 src/client/views/nodes/DocumentView.tsx            | 12 ++++++---
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +--
 7 files changed, 31 insertions(+), 36 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6451fdf5e..4ab2ade8e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -424,23 +424,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             document.addEventListener("pointermove", this.onRadiusMove);
             document.addEventListener("pointerup", this.onRadiusUp);
         }
-        if (!this._isMoving) {
-            SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
-                map(d => d.borderRounding = "0%");
-        }
     }
 
     onRadiusMove = (e: PointerEvent): void => {
         this._isMoving = true;
         let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
-        SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
-            map(d => d.borderRounding = `${Math.min(100, dist)}%`);
+        dist = dist < 3 ? 0 : dist;
+        let usingRule = false;
         SelectionManager.SelectedDocuments().map(dv => {
             let cv = dv.props.ContainingCollectionView;
             let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
             let heading = NumCast(dv.props.Document.heading);
-            cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding));
+            ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
+            usingRule = usingRule || (ruleProvider && heading ? true : false);
         })
+        !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
+            map(d => d.borderRounding = `${Math.min(100, dist)}%`);
         e.stopPropagation();
         e.preventDefault();
     }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index aa573f16b..867735c0b 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -73,7 +73,7 @@ export class InkingControl extends React.Component {
                     let cv = view.props.ContainingCollectionView;
                     let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
                     let parback = cv && StrCast(cv.props.Document.backgroundColor);
-                    cv && parback && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
+                    cv && parback && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
                     // if (parback && cv && parback.indexOf("rgb") !== -1) {
                     //     let parcol = Utils.fromRGBAstr(parback);
                     //     let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 24f2a60a9..9a8ae3535 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -262,27 +262,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (heading === 0) {
             let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
                 DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
+            heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
         newBox.heading = heading;
 
-        PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => {
-            if (!ruleProvider) ruleProvider = this.props.Document;
-            // saturation shift
-            // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
-            // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor));
-            // let hsl = Utils.RGBToHSL(back.r, back.g, back.b);
-            // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l };
-            // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l)));
-            // OR transparency set
-            let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
-            (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col);
-
-            let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
-            round && (Doc.GetProto(newBox).borderRounding = round);
-            newBox.ruleProvider = ruleProvider;
-            this.addDocument(newBox, false);
-        });
+        if (Cast(this.props.Document.ruleProvider, Doc) as Doc) {
+            newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc);
+        }
+        this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
@@ -898,7 +885,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onContextMenu = (e: React.MouseEvent) => {
         let layoutItems: ContextMenuProps[] = [];
 
-        if (this.childDocs.some(d => d.isTemplate)) {
+        if (this.childDocs.some(d => BoolCast(d.isTemplate))) {
             layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
         }
         layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
@@ -913,9 +900,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             },
             icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
-        this.props.Document.useClusters && layoutItems.push({
-            description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
-            event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
+        layoutItems.push({
+            description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`,
+            event: () => this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider,
             icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
         });
         layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index fe48a3485..4308497a1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -356,7 +356,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
-                newCollection.ruleProvider = this.props.container.props.Document;
+                newCollection.ruleProvider = this.props.container.props.Document.isRuleProvider ? this.props.container.props.Document : this.props.container.props.Document.ruleProvider;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 07dd1cae7..082e5c5e3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -74,7 +74,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
+        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
+        br = !br && ruleRounding ? ruleRounding : br;
         if (br.endsWith("%")) {
             let percent = Number(br.substr(0, br.length - 1)) / 100;
             let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3f0b62511..7b9ed12a7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -793,9 +793,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
 
     render() {
-        let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ?
+        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined;
+        let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
+        let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor;
+        let clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground;
+
+        let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
-            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
+            ruleColor && !colorSet ? ruleColor : 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%";
@@ -811,7 +817,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
         let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding);
+        let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding, ruleRounding);
         let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
         let searchHighlight = (!this.props.Document.search_fields ? (null) :
             <div key="search" style={{ position: "absolute", background: "yellow", bottom: "-20px", borderRadius: "5px", transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 444b91b28..658e2a04d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -284,8 +284,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") {
-                this.props.Document.layout = draggedDoc;
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
+                this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
             }
-- 
cgit v1.2.3-70-g09d2


From 233893698083cbcfcf39ddad8b57049aeb1ba842 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 14:18:55 -0400
Subject: refactored how ruleProvider's work.   overloaded custom template  for
 creating metadata fields

---
 src/client/util/RichTextRules.ts                   |  8 +++----
 src/client/util/TooltipTextMenu.tsx                |  4 ++--
 src/client/views/DocumentDecorations.tsx           | 11 ++-------
 src/client/views/InkingControl.tsx                 |  5 ++--
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 src/client/views/MainView.tsx                      |  2 ++
 src/client/views/TemplateMenu.tsx                  | 16 ++++++-------
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionSchemaView.tsx     |  1 +
 src/client/views/collections/CollectionSubView.tsx |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 +++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 --
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 13 +++++++----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 .../views/presentationview/PresentationElement.tsx |  1 +
 src/client/views/search/SearchItem.tsx             |  1 +
 src/new_fields/Doc.ts                              |  2 +-
 19 files changed, 51 insertions(+), 51 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c0c62463a..c727eec73 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -61,7 +61,7 @@ export const inpRules = {
             new RegExp(/^#([0-9]+)\s$/),
             (state, match, start, end) => {
                 let size = Number(match[1]);
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]);
@@ -74,7 +74,7 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "center";
@@ -88,7 +88,7 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "left";
@@ -100,7 +100,7 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
-                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "right";
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c376b6f86..84d045e6f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -496,7 +496,7 @@ export class TooltipTextMenu {
             if (markType.name[0] === 'p') {
                 let size = this.fontSizeToNum.get(markType);
                 if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = this.editorProps.ruleProvider;
                 let heading = NumCast(this.editorProps.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleSize_" + heading] = size;
@@ -505,7 +505,7 @@ export class TooltipTextMenu {
             else {
                 let fontName = this.fontStylesToName.get(markType);
                 if (fontName) { this.updateFontStyleDropdown(fontName); }
-                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = this.editorProps.ruleProvider;
                 let heading = NumCast(this.editorProps.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleFont_" + heading] = fontName;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6d63e8f73..ebdf2a749 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -365,14 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
                     let docViews = SelectionManager.ViewsSortedVertically();
                     let topDocView = docViews[0];
-                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                    if (ind !== -1) {
-                        topDocView.templates.splice(ind, 1);
-                        topDocView.props.Document.subBulletDocs = undefined;
-                    } else {
-                        topDocView.addTemplate(Templates.Bullet);
-                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
-                    }
+                    topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
                 }
             }
             this._removeIcon = false;
@@ -439,7 +432,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let usingRule = false;
         SelectionManager.SelectedDocuments().map(dv => {
             let cv = dv.props.ContainingCollectionView;
-            let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
+            let ruleProvider = cv && cv.props.ruleProvider;
             let heading = NumCast(dv.props.Document.heading);
             ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
             usingRule = usingRule || (ruleProvider && heading ? true : false);
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 867735c0b..86d0fc0be 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -71,9 +71,8 @@ export class InkingControl extends React.Component {
                     targetDoc.backgroundColor = this._selectedColor;
                 if (view.props.Document.heading) {
                     let cv = view.props.ContainingCollectionView;
-                    let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
-                    let parback = cv && StrCast(cv.props.Document.backgroundColor);
-                    cv && parback && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
+                    let ruleProvider = cv && (Cast(cv.props.ruleProvider, Doc) as Doc);
+                    cv && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
                     // if (parback && cv && parback.indexOf("rgb") !== -1) {
                     //     let parcol = Utils.fromRGBAstr(parback);
                     //     let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index c3a2cb214..71fb2707d 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -72,7 +72,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         if (this._textTargetDiv) {
             this._textTargetDiv.style.color = this._textColor;
         }
-        this._textAutoHeight = autoHeight;
         this.TextFieldKey = textFieldKey!;
         let txf = tx ? tx : () => Transform.Identity();
         this._textXf = txf;
@@ -143,6 +142,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                                 Document={FormattedTextBox.InputBoxOverlay.props.Document}
                                 DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                                 onClick={undefined}
+                                ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined}
                                 ChromeHeight={this.ChromeHeight}
                                 isSelected={returnTrue} select={emptyFunction} renderDepth={0}
                                 ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b64986084..2cec1c052 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -322,6 +322,7 @@ export class MainView extends React.Component {
                             addDocTab={emptyFunction}
                             pinToPres={emptyFunction}
                             onClick={undefined}
+                            ruleProvider={undefined}
                             removeDocument={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
@@ -385,6 +386,7 @@ export class MainView extends React.Component {
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             removeDocument={undefined}
+            ruleProvider={undefined}
             onClick={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 0ef1a137d..060191e29 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -51,16 +51,16 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
     dragRef = React.createRef<HTMLUListElement>();
 
-    constructor(props: TemplateMenuProps) {
-        super(props);
-    }
-
     toggleCustom = (e: React.MouseEvent): void => {
         this.props.docs.map(dv => {
-            if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) {
-                dv.makeCustomViewClicked();
-            } else if (dv.Document.nativeLayout) {
-                dv.makeNativeViewClicked();
+            if (dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.DataDoc) {
+                Doc.MakeMetadataFieldTemplate(dv.props.Document, dv.props.ContainingCollectionView.props.DataDoc)
+            } else {
+                if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) {
+                    dv.makeCustomViewClicked();
+                } else if (dv.Document.nativeLayout) {
+                    dv.makeNativeViewClicked();
+                }
             }
         });
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fb8b0c41b..166fa0811 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -631,6 +631,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             bringToFront={emptyFunction}
             addDocument={undefined}
             removeDocument={undefined}
+            ruleProvider={undefined}
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9d83aa6c1..dca1d7c1d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -995,6 +995,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
+                        ruleProvider={undefined}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 99e5ab7b3..270916522 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    ruleProvider: Doc | undefined;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9a8ae3535..4a3e5039a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -257,18 +257,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
-        let heading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
-        heading = heading === 0 || this.childDocs.length === 0 ? 1 : heading === 1 ? 2 : 0;
+        let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+        let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
         if (heading === 0) {
             let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
                 DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
-        }
-        newBox.heading = heading;
-
-        if (Cast(this.props.Document.ruleProvider, Doc) as Doc) {
-            newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc);
+            heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
+        !this.props.Document.isRuleProvider && (newBox.heading = heading);
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
@@ -698,6 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
+            ruleProvider: this.props.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider,
             onClick: this.props.onClick,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
@@ -723,6 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
+            ruleProvider: this.props.ruleProvider,
             onClick: this.props.onClick,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth,
@@ -817,6 +815,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     if (pair.layout && !(pair.data instanceof Promise)) {
                         prev.push({
                             ele: <CollectionFreeFormDocumentView key={doc[Id]}
+                                ruleProvider={this.props.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
                                 jitterRotation={NumCast(this.props.Document.jitterRotation)}
                                 x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                                 width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
@@ -873,6 +872,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }, "arrange contents");
     }
 
+    autoFormat = () => {
+        this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider;
+        this.childDocs.map(child => child.heading = undefined);
+    }
+
     analyzeStrokes = async () => {
         let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
         if (!data) {
@@ -900,11 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             },
             icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
-        layoutItems.push({
-            description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`,
-            event: () => this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider,
-            icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
-        });
+        layoutItems.push({ description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: !this.props.Document.isRuleProvider ? "chalkboard" : "chalkboard" });
         layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
@@ -1034,7 +1034,6 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get backgroundView() {
-        let props = this.props;
         return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
             renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4308497a1..e46e8cb88 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -329,7 +329,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
                 summary.proto!.subBulletDocs = new List<Doc>(selected);
-                summary.templates = new List<string>([Templates.Bullet.Layout]);
                 let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                 container.viewType = CollectionViewType.Stacking;
                 container.autoHeight = true;
@@ -356,7 +355,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
-                newCollection.ruleProvider = this.props.container.props.Document.isRuleProvider ? this.props.container.props.Document : this.props.container.props.Document.ruleProvider;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 082e5c5e3..4872a7aa1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -74,7 +74,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
-        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleProvider = this.props.ruleProvider;
         let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
         br = !br && ruleRounding ? ruleRounding : br;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0816cb813..cc04c5a9f 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, trace, observable } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -9,12 +9,13 @@ import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from '../../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
@@ -35,12 +36,10 @@ import { MainView } from '../MainView';
 import { OverlayView } from '../OverlayView';
 import { ScriptBox } from '../ScriptBox';
 import { ScriptingRepl } from '../ScriptingRepl';
-import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
-import { DocumentType } from '../../documents/DocumentTypes';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -89,6 +88,7 @@ export interface DocumentViewProps {
     renderDepth: number;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     ContentScaling: () => number;
+    ruleProvider: Doc | undefined;
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
@@ -470,6 +470,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this.props.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) :
                         Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
 
+                fieldTemplate.backgroundColor = StrCast(this.props.Document.backgroundColor);
+                fieldTemplate.heading = 1;
+
                 let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) });
                 let proto = Doc.GetProto(docTemplate);
                 Doc.MakeMetadataFieldTemplate(fieldTemplate, proto);
@@ -800,7 +803,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
 
     render() {
-        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleProvider = this.props.ruleProvider;
         let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined;
         let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d9774303b..943d181d6 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,6 +30,7 @@ export interface FieldViewProps {
     leaveNativeSize?: boolean;
     fitToBox?: boolean;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+    ruleProvider: Doc | undefined;
     Document: Doc;
     DataDoc?: Doc;
     onClick?: ScriptField;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d39291743..a0dc054cf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -458,7 +458,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
         this._rulesReactionDisposer = reaction(() => {
-            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
+            let ruleProvider = this.props.ruleProvider;
             let heading = NumCast(this.props.Document.heading);
             if (ruleProvider instanceof Doc) {
                 return {
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 80aa25f48..7be44faf6 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -351,6 +351,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     Document={this.props.document}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
+                    ruleProvider={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index c56d093fa..cd3dd912c 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -165,6 +165,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     Document={this.props.doc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
+                    ruleProvider={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1a3d689bb..5b22a62a1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -560,7 +560,7 @@ export namespace Doc {
 
     export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
-        let metadataFieldName = StrCast(fieldTemplate.title);
+        let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
         if (fieldTemplate.layout instanceof Doc) {
-- 
cgit v1.2.3-70-g09d2


From dd7679295b84ceba49b8d581bb64f97cc1a86fbb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 17:30:02 -0400
Subject: more rule provider fixes

---
 src/client/views/DocumentDecorations.tsx                        | 2 +-
 src/client/views/InkingControl.tsx                              | 8 +++++++-
 src/client/views/collections/CollectionSchemaCells.tsx          | 1 +
 src/client/views/collections/CollectionSchemaView.tsx           | 5 ++++-
 src/client/views/collections/CollectionStackingView.tsx         | 1 +
 src/client/views/collections/CollectionTreeView.tsx             | 3 +++
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                         | 2 +-
 src/client/views/nodes/FormattedTextBox.tsx                     | 2 ++
 src/client/views/nodes/KeyValuePair.tsx                         | 1 +
 10 files changed, 22 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ebdf2a749..ac103b2ea 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -848,7 +848,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template => {
             let checked = false;
-            SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.props.Document["show" + template.Name] !== undefined));
+            SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined));
             templates.set(template, checked);
         });
 
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 86d0fc0be..94cc1f06c 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -51,7 +51,13 @@ export class InkingControl extends React.Component {
             let oldColors = selected.map(view => {
                 let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
                 let oldColor = StrCast(targetDoc.backgroundColor);
-                if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) {
+                if (view.props.ContainingCollectionView) {
+                    if (!view.props.ContainingCollectionView.props.Document.colorPalette) {
+                        let defaultPalette = ["rg14,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+                            "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+                        let colorPalette = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string"));
+                        if (!colorPalette) view.props.ContainingCollectionView.props.Document.colorPalette = new List<string>(defaultPalette);
+                    }
                     let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[];
                     let closest = 0;
                     let dist = 10000000;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index c59107b53..17a3f4f7c 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -149,6 +149,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             DataDoc: this.props.rowProps.original,
             fieldKey: this.props.rowProps.column.id as string,
             fieldExt: "",
+            ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index dca1d7c1d..1a84f94c8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -32,6 +32,7 @@ import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, Collection
 import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { DocumentType } from "../../documents/DocumentTypes";
 
 
 library.add(faCog, faPlus, faSortUp, faSortDown);
@@ -161,6 +162,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
                 childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
+                ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
                 width={this.previewWidth}
                 height={this.previewHeight}
                 getTransform={this.getPreviewTransform}
@@ -901,6 +903,7 @@ interface CollectionSchemaPreviewProps {
     fitToBox?: boolean;
     width: () => number;
     height: () => number;
+    ruleProvider: Doc | undefined;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
     onClick?: ScriptField;
@@ -995,7 +998,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
-                        ruleProvider={undefined}
+                        ruleProvider={this.props.ruleProvider}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 91e10b0ac..89cccedff 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -144,6 +144,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             DataDocument={dataDoc}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth}
+            ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
             fitToBox={this.props.fitToBox}
             onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler}
             width={width}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f5bb76966..b1e063997 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -37,6 +37,7 @@ export interface TreeViewProps {
     containingCollection: Doc;
     renderDepth: number;
     deleteDoc: (doc: Doc) => boolean;
+    ruleProvider: Doc | undefined;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
@@ -324,6 +325,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     DataDocument={this.resolvedDataDoc}
                     renderDepth={this.props.renderDepth}
                     showOverlays={this.noOverlays}
+                    ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     width={this.docWidth}
                     height={this.docHeight}
@@ -491,6 +493,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 dataDoc={pair.data}
                 containingCollection={containingCollection}
                 treeViewId={treeViewId}
+                ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
                 key={child[Id]}
                 indentDocument={indent}
                 renderDepth={renderDepth}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e46e8cb88..cc5e887b2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -287,7 +287,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]);
             let usedPaletted = new Map<string, number>();
             [...this.props.activeDocuments(), this.props.container.props.Document].map(child => {
-                let bg = StrCast(child.backgroundColor);
+                let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor);
                 if (palette.indexOf(bg) !== -1) {
                     palette.splice(palette.indexOf(bg), 1);
                     if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc04c5a9f..591a507eb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -811,7 +811,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
-            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
+            ruleColor ? ruleColor : 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%";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a0dc054cf..ffb829825 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -285,6 +285,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
+                // let m = Doc.MakeDelegate(draggedDoc);  // under construction
+                // m.layout = m.layout.replace(/fieldKey={})
                 this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index a27dbd83d..7e0f3735d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -55,6 +55,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             Document: this.props.doc,
             DataDoc: this.props.doc,
             ContainingCollectionView: undefined,
+            ruleProvider: undefined,
             fieldKey: this.props.keyName,
             fieldExt: "",
             isSelected: returnFalse,
-- 
cgit v1.2.3-70-g09d2


From 827cee4d7ac4f1e761b141097d20671e7873bea8 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 15 Sep 2019 01:14:21 -0400
Subject: changed moveDocument to move the draggedDoc -- its up to the caller
 to add the droppedDoc if it wants to. cleaned up some old code related to
 passing dataDoc along with doc's to drag and other functinos

---
 src/client/util/DragManager.ts                     | 16 ++++-----
 src/client/views/DocumentDecorations.tsx           | 20 +++++------
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 src/client/views/TemplateMenu.tsx                  |  2 +-
 .../views/collections/CollectionBaseView.tsx       | 13 ++++---
 .../views/collections/CollectionDockingView.tsx    | 14 +++-----
 .../views/collections/CollectionSchemaCells.tsx    |  3 +-
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../CollectionStackingViewFieldColumn.tsx          |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  8 +++--
 src/client/views/collections/CollectionView.tsx    |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 40 +++++++---------------
 src/client/views/linking/LinkMenuGroup.tsx         |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 35 ++++++-------------
 src/client/views/nodes/DragBox.tsx                 |  2 +-
 src/client/views/pdf/Page.tsx                      |  2 +-
 src/client/views/search/SearchItem.tsx             |  2 +-
 18 files changed, 67 insertions(+), 103 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4c9c9c17c..252accefa 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -32,7 +32,7 @@ export function SetupDrag(
         document.removeEventListener("pointermove", onRowMove);
         document.removeEventListener('pointerup', onRowUp);
         let doc = await docFunc();
-        var dragData = new DragManager.DocumentDragData([doc], [undefined]);
+        var dragData = new DragManager.DocumentDragData([doc]);
         dragData.dropAction = dropAction;
         dragData.moveDocument = moveFunc;
         dragData.options = options;
@@ -76,7 +76,7 @@ export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: num
     if (draggeddoc) {
         let moddrag = await Cast(draggeddoc.annotationOn, Doc);
         let dragdocs = moddrag ? [moddrag] : [draggeddoc];
-        let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+        let dragData = new DragManager.DocumentDragData(dragdocs);
         dragData.moveDocument = moveLinkedDocument;
         DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
             handlers: {
@@ -107,7 +107,7 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n
             if (doc) moddrag.push(doc);
         }
         let dragdocs = moddrag.length ? moddrag : draggedDocs;
-        let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+        let dragData = new DragManager.DocumentDragData(dragdocs);
         dragData.moveDocument = moveLinkedDocument;
         DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
             handlers: {
@@ -201,15 +201,13 @@ export namespace DragManager {
 
     export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     export class DocumentDragData {
-        constructor(dragDoc: Doc[], dragDataDocs: (Doc | undefined)[]) {
+        constructor(dragDoc: Doc[]) {
             this.draggedDocuments = dragDoc;
-            this.draggedDataDocs = dragDataDocs;
             this.droppedDocuments = dragDoc;
             this.xOffset = 0;
             this.yOffset = 0;
         }
         draggedDocuments: Doc[];
-        draggedDataDocs: (Doc | undefined)[];
         droppedDocuments: Doc[];
         xOffset: number;
         yOffset: number;
@@ -253,7 +251,7 @@ export namespace DragManager {
     }
 
     export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize?: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
-        let dragData = new DragManager.DocumentDragData([], [undefined]);
+        let dragData = new DragManager.DocumentDragData([]);
         runInAction(() => StartDragFunctions.map(func => func()));
         StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
             (dropData: { [id: string]: any }) => {
@@ -363,8 +361,6 @@ export namespace DragManager {
 
         const docs: Doc[] =
             dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
-        const datadocs: (Doc | undefined)[] =
-            dragData instanceof DocumentDragData ? dragData.draggedDataDocs : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
         let dragElements = eles.map(ele => {
             const w = ele.offsetWidth,
                 h = ele.offsetHeight;
@@ -449,7 +445,7 @@ export namespace DragManager {
                     pageY: e.pageY,
                     preventDefault: emptyFunction,
                     button: 0
-                }, docs, datadocs);
+                }, docs);
             }
             //TODO: Why can't we use e.movementX and e.movementY?
             let moveX = e.pageX - lastX;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ac103b2ea..ac1f51688 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -253,7 +253,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dragDocView = SelectionManager.SelectedDocuments()[0];
         const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
         const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
-        let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document), SelectionManager.SelectedDocuments().map(dv => dv.props.DataDoc ? dv.props.DataDoc : dv.props.Document));
+        let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
         dragData.xOffset = xoff;
         dragData.yOffset = yoff;
         dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
@@ -358,15 +358,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) {
                 selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
             }
-            if (!this._removeIcon) {
-                if (selectedDocs.length === 1) {
-                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].toggleMinimized());
-                } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
-                    Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
-                    let docViews = SelectionManager.ViewsSortedVertically();
-                    let topDocView = docViews[0];
-                    topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
-                }
+            if (!this._removeIcon && selectedDocs.length === 1) { // if you click on the top-left button when just 1 doc is selected, then collapse it.  not sure why we don't do it for multiple selections
+                this.getIconDoc(selectedDocs[0]).then(async icon => {
+                    let minimizedDoc = await Cast(selectedDocs[0].props.Document.minimizedDoc, Doc);
+                    if (minimizedDoc) {
+                        let scrpt = selectedDocs[0].props.ScreenToLocalTransform().scale(selectedDocs[0].props.ContentScaling()).inverse().transformPoint(
+                            NumCast(minimizedDoc.x) - NumCast(selectedDocs[0].Document.x), NumCast(minimizedDoc.y) - NumCast(selectedDocs[0].Document.y));
+                        selectedDocs[0].collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
+                    }
+                });
             }
             this._removeIcon = false;
         }
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 71fb2707d..cd8423a12 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -102,7 +102,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) {
             document.removeEventListener("pointermove", this.textBoxMove);
             document.removeEventListener('pointerup', this.textBoxUp);
-            let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document], [FormattedTextBox.InputBoxOverlay.props.DataDoc]);
+            let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]);
             const [left, top] = this._textXf().inverse().transformPoint(0, 0);
             dragData.xOffset = e.clientX - left;
             dragData.yOffset = e.clientY - top;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index af3fcaa24..d57a72dad 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -67,7 +67,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             setTimeout(() => {
                 let newDocView = DocumentManager.Instance.getDocumentView(topDoc);
                 if (newDocView) {
-                    let de = new DragManager.DocumentDragData([topDoc], [undefined]);
+                    let de = new DragManager.DocumentDragData([topDoc]);
                     de.moveDocument = topDocView.props.moveDocument;
                     let xf = newDocView.ContentDiv!.getBoundingClientRect();
                     DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index b7036b3ff..a54718e9e 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -141,17 +141,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         return false;
     }
 
+    // this is called with the document that was dragged and the collection to move it into.
+    // if the target collection is the same as this collection, then the move will be allowed.
+    // otherwise, the document being moved must be able to be removed from its container before
+    // moving it into the target.  
     @action.bound
     moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-        let self = this;
-        let targetDataDoc = this.props.Document;
-        if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) {
+        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
-        if (this.removeDocument(doc)) {
-            return addDocument(doc);
-        }
-        return false;
+        return this.removeDocument(doc) ? addDocument(doc) : false;
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 166fa0811..bf55dba31 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -66,15 +66,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     }
     hack: boolean = false;
     undohack: any = null;
-    public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) {
+    public StartOtherDrag(e: any, dragDocs: Doc[]) {
         let config: any;
         if (dragDocs.length === 1) {
-            config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]);
+            config = CollectionDockingView.makeDocumentConfig(dragDocs[0], undefined);
         } else {
             config = {
                 type: 'row',
                 content: dragDocs.map((doc, i) => {
-                    CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]);
+                    CollectionDockingView.makeDocumentConfig(doc, undefined);
                 })
             };
         }
@@ -84,12 +84,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             dragSource.destroy();
         });
         dragSource._dragListener.onMouseDown(e);
-        // dragSource.destroy();
-        // this.hack = true;
-        // this.undohack = UndoManager.StartBatch("goldenDrag");
-        // dragDocs.map((dragDoc, i) =>
-        //     this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener.
-        //         onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
     @action
@@ -412,7 +406,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     e => {
                         e.preventDefault();
                         e.stopPropagation();
-                        DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, {
+                        DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
                             handlers: { dragComplete: emptyFunction },
                             hideSource: false
                         });
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 17a3f4f7c..3452e8702 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -172,7 +172,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         let onItemDown = (e: React.PointerEvent) => {
             if (fieldIsDoc) {
                 SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
-                    this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
+                    this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
+                    this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
             }
         };
         let onPointerEnter = (e: React.PointerEvent): void => {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 1a84f94c8..25d3bd128 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -14,7 +14,7 @@ import { listSpec } from "../../../new_fields/Schema";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { Gateway } from "../../northstar/manager/Gateway";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
 import { CompileScript, ts, Transformer } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 185bec7a2..34f2652ff 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -183,7 +183,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             if (compiled.compiled) {
                 let scriptField = new ScriptField(compiled);
                 alias.viewSpecScript = scriptField;
-                let dragData = new DragManager.DocumentDragData([alias], [alias.proto]);
+                let dragData = new DragManager.DocumentDragData([alias]);
                 DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
             }
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 270916522..bc994cffd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -126,9 +126,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (de.data.dropAction || de.data.userDropAction) {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } 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.Document, this.props.addDocument) || added, false);
+                    let movedDocs = de.data.draggedDocuments;// de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
+                    // note that it's possible the drag function might create a drop document that's not the same as the
+                    // original dragged document.  So we explicitly call addDocument() with a droppedDocument and 
+                    added = movedDocs.reduce((added: boolean, d, i) =>
+                        de.data.moveDocument(d, this.props.Document, (doc: Doc) => this.props.addDocument(de.data.droppedDocuments[i])) || added, false);
                 } else {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index bce4eb427..548f663ec 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -95,8 +95,9 @@ export class CollectionView extends React.Component<FieldViewProps> {
             }
             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: "Stacking", event: () => {
+                description: "Stacking (AutoHeight)", event: () => {
                     this.props.Document.viewType = CollectionViewType.Stacking;
                     this.props.Document.autoHeight = true
                 }, icon: "ellipsis-v"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ad91eb007..f2ca645a7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -381,7 +381,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             SelectionManager.DeselectAll();
             prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
 
-            let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+            let de = new DragManager.DocumentDragData(eles);
             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);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index cc5e887b2..5cab6f8e0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -315,7 +315,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
             this.marqueeInkDelete(inkData);
 
-            if (e.key === "s") {
+            if (e.key === "s" || e.key === "S") {
                 selected.map(d => {
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
@@ -324,35 +324,19 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     return d;
                 });
                 newCollection.chromeStatus = "disabled";
-                let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
-                newCollection.proto!.summaryDoc = summary;
-                selected = [newCollection];
-                newCollection.x = bounds.left + bounds.width;
-                summary.proto!.subBulletDocs = new List<Doc>(selected);
-                let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
-                container.viewType = CollectionViewType.Stacking;
-                container.autoHeight = true;
-                this.props.addLiveTextDocument(container);
-                // });
-            } else if (e.key === "S") {
-                selected.map(d => {
-                    this.props.removeDocument(d);
-                    d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
-                    d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
-                    d.page = -1;
-                    return d;
-                });
-                newCollection.chromeStatus = "disabled";
-                let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+                let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+                summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
                 newCollection.proto!.summaryDoc = summary;
-                selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
-                //this.props.addDocument(newCollection, false);
-                summary.proto!.summarizedDocs = new List<Doc>(selected);
-                summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-                summary.autoHeight = true;
-
-                this.props.addLiveTextDocument(summary);
+                if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
+                    let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
+                    container.viewType = CollectionViewType.Stacking;
+                    container.autoHeight = true;
+                    this.props.addLiveTextDocument(container);
+                } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
+                    summary.proto!.summarizedDocs = new List<Doc>([newCollection]);
+                    this.props.addLiveTextDocument(summary);
+                }
             }
             else {
                 this.props.addDocument(newCollection, false);
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index b6a24b0c8..d711ac284 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -57,7 +57,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                     let opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
                     if (opp) return opp;
                 }) as Doc[];
-                let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined));
+                let dragData = new DragManager.DocumentDragData(draggedDocs);
 
                 DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, {
                     handlers: {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e95f484a4..17bf02448 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -138,7 +138,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _downY: number = 0;
     private _lastTap: number = 0;
     private _doubleTap = false;
-    private _hitExpander = false;
     private _hitTemplateDrag = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -221,20 +220,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     get dataDoc() {
-        if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
-            // if there is no dataDoc (ie, we're not rendering a temlplate 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;
-        }
+        // bcz: don't think we need this, but left it in in case strange behavior pops up.  DocumentContentsView has this functionality
+        // if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
+        //     // if there is no dataDoc (ie, we're not rendering a temlplate 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.Document ? this.props.DataDoc : undefined;
     }
-    startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, applyAsTemplate?: boolean) {
+    startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) {
         if (this._mainCont.current) {
-            let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
-            let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
-            let dragData = new DragManager.DocumentDragData(allConnected, alldataConnected);
+            let dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.xOffset = xoff;
@@ -249,14 +247,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
     }
-    toggleMinimized = async () => {
-        let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
-        if (minimizedDoc) {
-            let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(
-                NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
-            this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
-        }
-    }
 
     static _undoBatch?: UndoManager.Batch = undefined;
     @action
@@ -315,15 +305,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             SelectionManager.SelectDoc(this, e.ctrlKey);
             let isExpander = (e.target as any).id === "isExpander";
             if (BoolCast(this.props.Document.isButton) || this.props.Document.type === DocumentType.BUTTON || isExpander) {
-                let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
                 let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
                 let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
                 let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
                 let expandedDocs: Doc[] = [];
-                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
                 expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
                 expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
-                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
+                // let expandedDocs = [ ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
                 if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     SelectionManager.DeselectAll();
                     let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
@@ -394,7 +382,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.nativeEvent.cancelBubble) return;
         this._downX = e.clientX;
         this._downY = e.clientY;
-        this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
         this._hitTemplateDrag = false;
         for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
             if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
@@ -416,7 +403,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
                 }
             }
             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
diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx
index 1f2c88086..067d47de4 100644
--- a/src/client/views/nodes/DragBox.tsx
+++ b/src/client/views/nodes/DragBox.tsx
@@ -55,7 +55,7 @@ export class DragBox extends DocComponent<FieldViewProps, DragDocument>(DragDocu
             let doc = res !== undefined && res.success ?
                 res.result as Doc :
                 Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" });
-            doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc], [undefined]), e.clientX, e.clientY);
+            doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
         }
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 856e883e7..533247170 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -137,7 +137,7 @@ export default class Page extends React.Component<IPageProps> {
         view.nativeWidth = this.props.Document.nativeWidth;
         view.startY = marquee.top + this.props.getScrollFromPage(this.props.page);
         view.width = this.props.Document[WidthSym]();
-        DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view], [undefined]), 0, 0);
+        DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
     }
 
     @action
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index cd3dd912c..510672788 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -270,7 +270,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         e.stopPropagation();
         const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
-        DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, {
+        DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
             handlers: { dragComplete: emptyFunction },
             hideSource: false,
         });
-- 
cgit v1.2.3-70-g09d2


From 999cd7deb87ce5c6430a5f8e0e1721033736bbab Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 16 Sep 2019 17:13:16 -0400
Subject: cleaned up titling of icons and summaries.

---
 src/client/views/DocumentDecorations.tsx           |  6 ++-
 .../collections/collectionFreeForm/MarqueeView.tsx | 10 ++--
 src/client/views/nodes/DocumentView.tsx            | 22 +--------
 src/client/views/nodes/IconBox.tsx                 | 53 +++++++++++++---------
 4 files changed, 43 insertions(+), 48 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ac1f51688..240abaf6b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -32,6 +32,8 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { ObjectField } from '../../new_fields/ObjectField';
 import { DocServer } from '../DocServer';
+import { CompileScript } from '../util/Scripting';
+import { ComputedField } from '../../new_fields/ScriptField';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -378,8 +380,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let doc = selected[0].props.Document;
         let iconDoc = Docs.Create.IconDocument(layoutString);
         iconDoc.isButton = true;
-        iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon";
-        iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
+
+        IconBox.AutomaticTitle(iconDoc);
         //iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
         iconDoc.proto!.isMinimized = false;
         iconDoc.width = Number(MINIMIZED_ICON_SIZE);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5cab6f8e0..d74fbafb3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -23,6 +23,8 @@ import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHe
 import { string } from "prop-types";
 import { listSpec } from "../../../../new_fields/Schema";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import { CompileScript } from "../../../util/Scripting";
+import { ComputedField } from "../../../../new_fields/ScriptField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -309,7 +311,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
                 width: bounds.width,
                 height: bounds.height,
-                title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
+                title: "a nested collection",
             });
             let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
             dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
@@ -325,9 +327,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 });
                 newCollection.chromeStatus = "disabled";
                 let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
-                summary.proto!.maximizeLocation = "inTab";  // or "inPlace", or "onRight"
-                newCollection.proto!.summaryDoc = summary;
+                Doc.GetProto(summary).maximizeLocation = "inTab";  // or "inPlace", or "onRight"
+                Doc.GetProto(newCollection).summaryDoc = summary;
                 newCollection.x = bounds.left + bounds.width;
+                let computed = CompileScript(`return summaryTitle(this);`, { params: { this: "Doc" }, typecheck: false });
+                computed.compiled && (Doc.GetProto(newCollection).title = new ComputedField(computed));
                 if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
                     let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                     container.viewType = CollectionViewType.Stacking;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 760f4d584..50a9aa326 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -141,13 +141,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _hitTemplateDrag = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
-    _animateToIconDisposer?: IReactionDisposer;
-    _reactionDisposer?: IReactionDisposer;
+    private _animateToIconDisposer?: IReactionDisposer;
 
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
     @computed get topMost(): boolean { return this.props.renderDepth === 0; }
-    screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
 
     @action
     componentDidMount() {
@@ -156,19 +154,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 handlers: { drop: this.drop.bind(this) }
             });
         }
-        // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
-        this._reactionDisposer = reaction(() => [DocListCast(this.props.Document.maximizedDocs).map(md => md.title),
-        this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
-            () => {
-                let maxDoc = DocListCast(this.props.Document.maximizedDocs);
-                if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
-                    this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon";
-                }
-                let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
-                if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) {
-                    this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded";
-                }
-            }, { fireImmediately: true });
         this._animateToIconDisposer = reaction(() => this.props.Document.isIconAnimating, (values) =>
             (values instanceof List) && this.animateBetweenIcon(values, values[2], values[3] ? true : false)
             , { fireImmediately: true });
@@ -209,16 +194,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @action
     componentWillUnmount() {
-        this._reactionDisposer && this._reactionDisposer();
         this._animateToIconDisposer && this._animateToIconDisposer();
         this._dropDisposer && this._dropDisposer();
         DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
     }
 
-    stopPropagation = (e: React.SyntheticEvent) => {
-        e.stopPropagation();
-    }
-
     get dataDoc() {
         // bcz: don't think we need this, but left it in in case strange behavior pops up.  DocumentContentsView has this functionality
         // if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 7e78ec684..ef9885bcf 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -12,6 +12,8 @@ import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
 import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
+import { Scripting, CompileScript } from "../../util/Scripting";
+import { ComputedField } from "../../../new_fields/ScriptField";
 
 
 library.add(faCaretUp);
@@ -27,6 +29,26 @@ export class IconBox extends React.Component<FieldViewProps> {
     @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
     @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
 
+    public static summaryTitleScript(inputDoc: Doc) {
+        const sumDoc = Cast(inputDoc.summaryDoc, Doc) as Doc;
+        if (sumDoc && StrCast(sumDoc.title).startsWith("-")) {
+            return sumDoc.title + ".expanded";
+        }
+        return "???";
+    }
+    public static titleScript(inputDoc: Doc) {
+        const maxDoc = DocListCast(inputDoc.maximizedDocs);
+        if (maxDoc.length === 1 && StrCast(maxDoc[0].title).startsWith("-")) {
+            return maxDoc[0].title + ".icon";
+        }
+        return maxDoc.length > 1 ? "-multiple-.icon" : "???";
+    }
+
+    public static AutomaticTitle(doc: Doc) {
+        let computed = CompileScript(`return iconTitle(this);`, { params: { this: "Doc" }, typecheck: false });
+        computed.compiled && (Doc.GetProto(doc).title = new ComputedField(computed));
+    }
+
     public static DocumentIcon(layout: string) {
         let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
             layout.indexOf("ImageBox") !== -1 ? faImage :
@@ -38,35 +60,20 @@ export class IconBox extends React.Component<FieldViewProps> {
     }
 
     setLabelField = (): void => {
-        this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
-    }
-    setUseOwnTitleField = (): void => {
-        this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle);
+        this.props.Document.hideLabel = !this.props.Document.hideLabel;
     }
 
     specificContextMenu = (): void => {
-        ContextMenu.Instance.addItem({
-            description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon",
-            event: this.setLabelField,
-            icon: "tag"
-        });
-        let maxDocs = DocListCast(this.props.Document.maximizedDocs);
-        if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) {
-            ContextMenu.Instance.addItem({
-                description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label",
-                event: this.setUseOwnTitleField,
-                icon: "text-height"
-            });
+        let cm = ContextMenu.Instance;
+        cm.addItem({ description: this.props.Document.hideLabel ? "Show label with icon" : "Remove label from icon", event: this.setLabelField, icon: "tag" });
+        if (!this.props.Document.hideLabel) {
+            cm.addItem({ description: "Use Target Title", event: () => IconBox.AutomaticTitle(this.props.Document), icon: "text-height" });
         }
     }
     @observable _panelWidth: number = 0;
     @observable _panelHeight: number = 0;
     render() {
-        let labelField = StrCast(this.props.Document.labelField);
-        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) ? firstDoc[labelField] : this.props.Document.title);
+        let label = this.props.Document.hideLabel ? "" : this.props.Document.title;
         return (
             <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
                 {this.minimizedIcon}
@@ -82,4 +89,6 @@ export class IconBox extends React.Component<FieldViewProps> {
                 </Measure>
             </div>);
     }
-}
\ No newline at end of file
+}
+Scripting.addGlobal(function iconTitle(doc: any) { return IconBox.titleScript(doc); });
+Scripting.addGlobal(function summaryTitle(doc: any) { return IconBox.summaryTitleScript(doc); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 624d44ad339d55df66a4ed8bf4b2cb91608efeef Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 16 Sep 2019 17:45:30 -0400
Subject: fixed maximizing of summarized documents and docs in stacking views.

---
 src/client/views/collections/CollectionStackingView.tsx         | 2 +-
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 5 +++--
 src/client/views/nodes/DocumentView.tsx                         | 6 +++---
 3 files changed, 7 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 14a9dc9d9..0a96676e3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -163,7 +163,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         </CollectionSchemaPreview>;
     }
     getDocHeight(d?: Doc) {
-        if (!d) return 0;
+        if (!d || d.willMaximize) return 0;
         let nw = NumCast(d.nativeWidth);
         let nh = NumCast(d.nativeHeight);
         if (!d.ignoreAspect && nw && nh) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d74fbafb3..8decebe0d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -327,8 +327,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 });
                 newCollection.chromeStatus = "disabled";
                 let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
-                Doc.GetProto(summary).maximizeLocation = "inTab";  // or "inPlace", or "onRight"
                 Doc.GetProto(newCollection).summaryDoc = summary;
+                Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
                 newCollection.x = bounds.left + bounds.width;
                 let computed = CompileScript(`return summaryTitle(this);`, { params: { this: "Doc" }, typecheck: false });
                 computed.compiled && (Doc.GetProto(newCollection).title = new ComputedField(computed));
@@ -336,9 +336,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                     container.viewType = CollectionViewType.Stacking;
                     container.autoHeight = true;
+                    Doc.GetProto(summary).maximizeLocation = "inPlace";  // or "inPlace", or "onRight"
                     this.props.addLiveTextDocument(container);
                 } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
-                    summary.proto!.summarizedDocs = new List<Doc>([newCollection]);
+                    Doc.GetProto(summary).maximizeLocation = "inTab";  // or "inPlace", or "onRight"
                     this.props.addLiveTextDocument(summary);
                 }
             }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 50a9aa326..c9bd60d35 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -300,7 +300,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);
+                            let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !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) {
@@ -814,8 +814,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
             ruleColor && !colorSet ? ruleColor : 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%";
+        var nativeWidth = this.props.Document.willMaximize ? 0 : this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
+        var nativeHeight = this.props.Document.willMaximize ? 0 : 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 && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
         let showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : StrCast(this.layoutDoc.showCaption);
-- 
cgit v1.2.3-70-g09d2


From 088a16c9589e640e814d9120fecc0003ab9d594e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 18 Sep 2019 09:48:15 -0400
Subject: cleaned up some CompileScript code

---
 src/client/documents/Documents.ts                  |  6 ++--
 src/client/util/DragManager.ts                     | 10 +------
 .../views/collections/CollectionSchemaView.tsx     |  7 ++---
 .../CollectionStackingViewFieldColumn.tsx          | 10 ++-----
 .../views/collections/CollectionViewChromes.tsx    | 17 ++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx | 21 +++++---------
 src/client/views/nodes/DocumentView.tsx            | 15 ++--------
 src/client/views/nodes/IconBox.tsx                 |  5 ++--
 src/client/views/nodes/ImageBox.tsx                |  5 +---
 src/client/views/nodes/PDFBox.tsx                  |  6 ++--
 src/client/views/nodes/VideoBox.tsx                | 14 +--------
 src/new_fields/Doc.ts                              |  9 ++----
 src/new_fields/ScriptField.ts                      | 33 ++++++++++++++++++----
 13 files changed, 58 insertions(+), 100 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 206e2c4f1..99707db19 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -663,10 +663,8 @@ export namespace DocUtils {
 
             LinkManager.Instance.addLink(linkDocProto);
 
-            let script = `return links(this);`;
-            let computed = CompileScript(script, { params: { this: "Doc" }, typecheck: false });
-            computed.compiled && (Doc.GetProto(source).links = new ComputedField(computed));
-            computed.compiled && (Doc.GetProto(target).links = new ComputedField(computed));
+            Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)");
+            Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)");
         }, "make link");
         return linkDocProto;
     }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cb55e1bc6..bd7051993 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -256,15 +256,7 @@ export namespace DragManager {
         StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
             (dropData: { [id: string]: any }) => {
                 let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
-                let compiled = CompileScript(script, {
-                    params: { doc: Doc.name },
-                    typecheck: false,
-                    editable: true
-                });
-                if (compiled.compiled) {
-                    let scriptField = new ScriptField(compiled);
-                    bd.onClick = scriptField;
-                }
+                bd.onClick = ScriptField.MakeScript(script);
                 params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
                 initialize && initialize(bd);
                 bd.buttonParams = new List<string>(params);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 25d3bd128..5d09ade32 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -946,13 +946,10 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
-            let docDrag = de.data;
-            let computed = CompileScript("return this.image_data[0]", { params: { this: "Doc" } });
             this.props.childDocs && this.props.childDocs.map(otherdoc => {
-                let doc = docDrag.draggedDocuments[0];
                 let target = Doc.GetProto(otherdoc);
-                target.layout = target.detailedLayout = Doc.MakeDelegate(doc);
-                computed.compiled && (target.miniLayout = new ComputedField(computed));
+                target.layout = target.detailedLayout = Doc.MakeDelegate(de.data.draggedDocuments[0]);
+                target.miniLayout = ComputedField.MakeFunction("this.image_data[0]");
             });
             e.stopPropagation();
         }
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 34f2652ff..b3b7b40dd 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -178,13 +178,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             let key = StrCast(this.props.parent.props.Document.sectionFilter);
             let value = this.getValue(this._heading);
             value = typeof value === "string" ? `"${value}"` : value;
-            let script = `return doc.${key} === ${value}`;
-            let compiled = CompileScript(script, { params: { doc: Doc.name } });
-            if (compiled.compiled) {
-                let scriptField = new ScriptField(compiled);
-                alias.viewSpecScript = scriptField;
-                let dragData = new DragManager.DocumentDragData([alias]);
-                DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+            alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name });
+            if (alias.viewSpecScript) {
+                DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
             }
 
             e.stopPropagation();
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index c897af17e..7510b86a0 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -215,14 +215,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
             }
         }
         let fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
-            `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
-            `return (${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
-            "return true";
+            `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
+            `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
+            "true";
 
-        let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false });
-        if (compiled.compiled) {
-            this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled);
-        }
+        this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
     }
 
     @action
@@ -283,11 +280,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @action.bound
     clearFilter = () => {
-        let compiled = CompileScript("return true", { params: { doc: Doc.name }, typecheck: false });
-        if (compiled.compiled) {
-            this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled);
-        }
-
+        this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name });
         this._keyRestrictions = [];
         this.addKeyRestrictions([]);
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 8decebe0d..479e98840 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,30 +1,24 @@
-import * as htmlToImage from "html-to-image";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc";
-import { Id } from "../../../../new_fields/FieldSymbols";
+import { Doc, DocListCast } from "../../../../new_fields/Doc";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch } from "../../../util/UndoManager";
 import { InkingCanvas } from "../../InkingCanvas";
 import { PreviewCursor } from "../../PreviewCursor";
-import { Templates } from "../../Templates";
 import { CollectionViewType } from "../CollectionBaseView";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
-import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField";
-import { string } from "prop-types";
-import { listSpec } from "../../../../new_fields/Schema";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
-import { CompileScript } from "../../../util/Scripting";
-import { ComputedField } from "../../../../new_fields/ScriptField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -327,11 +321,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 });
                 newCollection.chromeStatus = "disabled";
                 let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
-                Doc.GetProto(newCollection).summaryDoc = summary;
                 Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
                 newCollection.x = bounds.left + bounds.width;
-                let computed = CompileScript(`return summaryTitle(this);`, { params: { this: "Doc" }, typecheck: false });
-                computed.compiled && (Doc.GetProto(newCollection).title = new ComputedField(computed));
+                Doc.GetProto(newCollection).summaryDoc = summary;
+                Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
                 if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
                     let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                     container.viewType = CollectionViewType.Stacking;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 152dc3ddd..16f8275c7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,7 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
-import { CompileScript, Scripting } from '../../util/Scripting';
+import { Scripting } from '../../util/Scripting';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -559,18 +559,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
         let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
-        onClicks.push({
-            description: "Toggle Detail", event: () => {
-                let compiled = CompileScript("toggleDetail(this)", {
-                    params: { this: "Doc" },
-                    typecheck: false,
-                    editable: true,
-                });
-                if (compiled.compiled) {
-                    this.Document.onClick = new ScriptField(compiled);
-                }
-            }, icon: "window-restore"
-        });
+        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), icon: "window-restore" });
         onClicks.push({ description: this.layoutDoc.ignoreClick ? "Select" : "Do Nothing", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 92cb5a9c9..63a504d1a 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -12,7 +12,7 @@ import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
 import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
-import { Scripting, CompileScript } from "../../util/Scripting";
+import { Scripting } from "../../util/Scripting";
 import { ComputedField } from "../../../new_fields/ScriptField";
 
 
@@ -45,8 +45,7 @@ export class IconBox extends React.Component<FieldViewProps> {
     }
 
     public static AutomaticTitle(doc: Doc) {
-        let computed = CompileScript(`return iconTitle(this);`, { params: { this: "Doc" }, typecheck: false });
-        computed.compiled && (Doc.GetProto(doc).title = new ComputedField(computed));
+        Doc.GetProto(doc).title = ComputedField.MakeFunction('iconTitle(this);');
     }
 
     public static DocumentIcon(layout: string) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 42307ee02..bbcc296e6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -17,7 +17,6 @@ import { Utils } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
-import { CompileScript } from '../../util/Scripting';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../../views/ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -244,9 +243,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             results.tags.map((tag: Tag) => {
                 tagsList.push(tag.name);
                 let sanitized = tag.name.replace(" ", "_");
-                let script = `return (${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`;
-                let computed = CompileScript(script, { params: { this: "Doc" } });
-                computed.compiled && (tagDoc[sanitized] = new ComputedField(computed));
+                tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
             });
             this.extensionDoc.generatedTags = tagsList;
             tagDoc.title = "Generated Tags Doc";
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 31e8f122b..64b84ba55 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -10,7 +10,6 @@ import { ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast } from "../../../new_fields/Types";
 import { PdfField } from "../../../new_fields/URLField";
 import { KeyCodes } from '../../northstar/utils/KeyCodes';
-import { CompileScript } from '../../util/Scripting';
 import { DocComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
 import { PDFViewer } from "../pdf/PDFViewer";
@@ -104,9 +103,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     private applyFilter = () => {
         let scriptText = this._scriptValue.length > 0 ? this._scriptValue :
             this._keyValue.length > 0 && this._valueValue.length > 0 ?
-                `return this.${this._keyValue} === ${this._valueValue}` : "return true";
-        let script = CompileScript(scriptText, { params: { this: Doc.name } });
-        script.compiled && (this.props.Document.filterScript = new ScriptField(script));
+                `this.${this._keyValue} === ${this._valueValue}` : "true";
+        this.props.Document.filterScript = ScriptField.MakeFunction(scriptText);
     }
 
     scrollTo = (y: number) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a696f255d..aa6be56bb 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -20,7 +20,6 @@ import { pageSchema } from "./ImageBox";
 import "./VideoBox.scss";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faVideo } from "@fortawesome/free-solid-svg-icons";
-import { CompileScript } from "../../util/Scripting";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
 var path = require('path');
@@ -116,18 +115,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                 x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
                 width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString()
             });
-            const script = CompileScript(`(self as any).curPage = ${NumCast(this.props.Document.curPage)}`, {
-                params: { this: Doc.name },
-                capturedVariables: { self: this.props.Document },
-                typecheck: false,
-                editable: true,
-            });
-            if (script.compiled) {
-                b.onClick = new ScriptField(script);
-                this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(b, false);
-            } else {
-                console.log(script.errors.map(error => error.messageText).join("\n"));
-            }
+            b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`);
         } else {
             //convert to desired file format
             var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index ffa9a4e98..98b3d824e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -2,7 +2,7 @@ import { observable, ObservableMap, runInAction, action } from "mobx";
 import { alias, map, serializable } from "serializr";
 import { DocServer } from "../client/DocServer";
 import { DocumentType } from "../client/documents/DocumentTypes";
-import { CompileScript, Scripting, scriptingGlobal } from "../client/util/Scripting";
+import { Scripting, scriptingGlobal } from "../client/util/Scripting";
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
 import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, Update } from "./FieldSymbols";
 import { List } from "./List";
@@ -426,12 +426,7 @@ export namespace Doc {
             alias.layout = Doc.MakeAlias(alias.layout);
         }
         let aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1;
-        let script = `return renameAlias(self, ${aliasNumber})`;
-        //let script = "StrCast(self.title).replace(/\\([0-9]*\\)/, \"\") + `(${n})`";
-        let compiled = CompileScript(script, { params: { this: "Doc" }, capturedVariables: { self: doc }, typecheck: false });
-        if (compiled.compiled) {
-            alias.title = new ComputedField(compiled);
-        }
+        alias.title = ComputedField.MakeFunction(`renameAlias(this, ${aliasNumber})`);
         return alias;
     }
 
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 83fb52d07..8f10766df 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -1,5 +1,5 @@
 import { ObjectField } from "./ObjectField";
-import { CompiledScript, CompileScript, scriptingGlobal } from "../client/util/Scripting";
+import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions } from "../client/util/Scripting";
 import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols";
 import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
@@ -101,6 +101,24 @@ export class ScriptField extends ObjectField {
     [ToScriptString]() {
         return "script field";
     }
+    public static CompileScript(script: string, params: object = {}, addReturn = false) {
+        let compiled = CompileScript(script, {
+            params: { this: Doc.name, ...params },
+            typecheck: false,
+            editable: true,
+            addReturn: addReturn
+        });
+        return compiled;
+    }
+    public static MakeFunction(script: string, params: object = {}) {
+        let compiled = ScriptField.CompileScript(script, params, true);
+        return compiled.compiled ? new ScriptField(compiled) : undefined;
+    }
+
+    public static MakeScript(script: string, params: object = {}) {
+        let compiled = ScriptField.CompileScript(script, params, false);
+        return compiled.compiled ? new ScriptField(compiled) : undefined;
+    }
 }
 
 @scriptingGlobal
@@ -109,11 +127,16 @@ export class ComputedField extends ScriptField {
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
     value = computedFn((doc: Doc) => {
         const val = this.script.run({ this: doc });
-        if (val.success) {
-            return val.result;
-        }
-        return undefined;
+        return val.success ? val.result : undefined;
     });
+    public static MakeScript(script: string, params: object = {}, ) {
+        let compiled = ScriptField.CompileScript(script, params, false);
+        return compiled.compiled ? new ComputedField(compiled) : undefined;
+    }
+    public static MakeFunction(script: string, params: object = {}) {
+        let compiled = ScriptField.CompileScript(script, params, true);
+        return compiled.compiled ? new ComputedField(compiled) : undefined;
+    }
 }
 
 export namespace ComputedField {
-- 
cgit v1.2.3-70-g09d2


From 4652881dbdffefab2240a2a0a509f8505376f91b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 18 Sep 2019 17:25:10 -0400
Subject: cleaned up navigate into portal and alt-dragging to copy contents

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  13 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/nodes/DocumentView.tsx            | 160 +++++++++------------
 src/client/views/nodes/FormattedTextBox.tsx        |   9 +-
 src/client/views/nodes/ImageBox.tsx                |  26 +---
 5 files changed, 92 insertions(+), 118 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 359731bda..baf907634 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -607,7 +607,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => {
+    focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
         const state = HistoryUtil.getState();
         // TODO This technically isn't correct if type !== "doc", as 
         // currently nothing is done, but we should probably push a new state
@@ -628,6 +628,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const newState = HistoryUtil.getState();
         newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
         HistoryUtil.pushState(newState);
+
+        let px = this.Document.panX;
+        let py = this.Document.panY;
+        let s = this.Document.scale;
         this.setPan(newPanX, newPanY);
 
         this.props.Document.panTransformType = "Ease";
@@ -635,6 +639,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (willZoom) {
             this.setScaleToZoom(doc, scale);
         }
+        afterFocus && setTimeout(() => {
+            if (afterFocus && afterFocus()) {
+                this.Document.panX = px;
+                this.Document.panY = py;
+                this.Document.scale = s;
+            }
+        }, 1000);
     }
 
     setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 479e98840..18d0fea8c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -329,7 +329,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                     container.viewType = CollectionViewType.Stacking;
                     container.autoHeight = true;
-                    Doc.GetProto(summary).maximizeLocation = "inPlace";  // or "inPlace", or "onRight"
+                    Doc.GetProto(summary).maximizeLocation = "inPlace";  // or "onRight"
                     this.props.addLiveTextDocument(container);
                 } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
                     Doc.GetProto(summary).maximizeLocation = "inTab";  // or "inPlace", or "onRight"
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 11924f86a..90dbe4c86 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -81,7 +81,7 @@ export interface DocumentViewProps {
     ruleProvider: Doc | undefined;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
+    focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
@@ -136,8 +136,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _dropDisposer?: DragManager.DragDropDisposer;
 
     public get ContentDiv() { return this._mainCont.current; }
-    @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
-    @computed get topMost(): boolean { return this.props.renderDepth === 0; }
+    @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+    @computed get topMost() { return this.props.renderDepth === 0; }
+    @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+    @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
 
     @action
     componentDidMount() {
@@ -280,19 +283,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         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 => {  // open up target if it's not already in view ...
-                                let cv = this.props.ContainingCollectionView;  // bcz: ugh --- maybe need to have a props.unfocus() method so that we leave things in the state we found them??
-                                let px = cv && cv.props.Document.panX;
-                                let py = cv && cv.props.Document.panY;
-                                let s = cv && cv.props.Document.scale;
-                                this.props.focus(this.props.Document, true, 1);  // by zooming into the button document first
-                                setTimeout(() => {
-                                    this.props.addDocTab(document, undefined, maxLocation);
-                                    cv && (cv.props.Document.panX = px);
-                                    cv && (cv.props.Document.panY = py);
-                                    cv && (cv.props.Document.scale = s);
-                                }, 1000); // then after the 1sec animation, open up the target in a new tab
-                            },
+                            // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
+                            doc => this.props.focus(this.props.Document, true, 1, () => { this.props.addDocTab(doc, undefined, maxLocation); return true; }),
                             linkedFwdPage[altKey ? 1 : 0], targetContext);
                     }
                 }
@@ -300,12 +292,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.nativeEvent.cancelBubble) return;
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitTemplateDrag = false;
+        // this whole section needs to move somewhere else.  We're trying to initiate a special "template" drag where
+        // this document is the template and we apply it to whatever we drop it on.
         for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
             if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
                 this._hitTemplateDrag = true;
@@ -386,16 +379,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.AnnotationDragData) {
+            /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner
             e.stopPropagation();
-            let annotationDoc = de.data.annotationDocument;
-            annotationDoc.linkedToDoc = true;
-            de.data.targetContext = this.props.ContainingCollectionView!.props.Document;
+            let sourceDoc = de.data.annotationDocument;
             let targetDoc = this.props.Document;
+            let annotations = await DocListCastAsync(sourceDoc.annotations);
+            sourceDoc.linkedToDoc = true;
+            de.data.targetContext = this.props.ContainingCollectionView!.props.Document;
             targetDoc.targetContext = de.data.targetContext;
-            let annotations = await DocListCastAsync(annotationDoc.annotations);
             annotations && annotations.forEach(anno => anno.target = targetDoc);
 
-            DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
+            DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(sourceDoc.title)}`);
         }
         if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
             Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc);
@@ -406,22 +400,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let destDoc = this.props.Document;
 
             e.stopPropagation();
-            if (de.mods === "AltKey") {
-                const protoDest = destDoc.proto;
-                const protoSrc = sourceDoc.proto;
-                let src = protoSrc ? protoSrc : sourceDoc;
-                let dst = protoDest ? protoDest : destDoc;
-                dst.data = (src.data! as ObjectField)[Copy]();
-                dst.nativeWidth = src.nativeWidth;
-                dst.nativeHeight = src.nativeHeight;
-            }
-            else {
-                // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
-                // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
-                let linkDoc = DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined);
-                de.data.droppedDocuments.push(destDoc);
-                de.data.linkDocument = linkDoc;
-            }
+            // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
+            // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
+            de.data.droppedDocuments.push(destDoc);
+            de.data.linkDocument = DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document);
         }
     }
 
@@ -448,13 +430,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             proto.nativeHeight = this.props.PanelHeight();
         }
     }
+
     @undoBatch
     @action
-    makeIntoPortal = (): void => {
-        if (!DocListCast(this.props.Document.links).find(doc => {
-            if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.Document.title + ".portal") return true;
-            return false;
-        })) {
+    makeIntoPortal = async () => {
+        let anchors = await Promise.all(DocListCast(this.props.Document.links).map(async (d: Doc) => await Cast(d.anchor2, Doc)));
+        if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) {
             let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
             DocServer.GetRefField(portalID).then(existingPortal => {
                 let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID });
@@ -463,17 +444,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
     }
+
     @undoBatch
     @action
     toggleCustomView = (): void => {
         if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) {
             Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc);
-        } else {
-            if (typeof this.props.Document.layout === "string") {
-                this.makeCustomViewClicked();
-            } else {
-                this.makeNativeViewClicked();
-            }
+        } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized
+            typeof this.props.Document.layout === "string" ? this.makeCustomViewClicked() : this.makeNativeViewClicked();
         }
     }
 
@@ -572,49 +550,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
         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: "Download document", icon: "download", event: async () => {
-                let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
+            description: "Download document", icon: "download", event: async () =>
+                console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
                     qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
-                }));
-                console.log(y);
-                // 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();
-            }
+                })))
+            // 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: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
-        type User = { email: string, userDocumentId: string };
-        let usersMenu: ContextMenuProps[] = [];
         try {
-            let stuff = await rp.get(Utils.prepend(RouteStore.getUsers));
-            const users: User[] = JSON.parse(stuff);
-            usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({
-                description: email, event: async () => {
+            type User = { email: string, userDocumentId: string };
+            const users: User[] = JSON.parse(await rp.get(Utils.prepend(RouteStore.getUsers)));
+            let usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({
+                description: email,
+                event: async () => {
                     const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc);
-                    if (!userDocument) {
-                        throw new Error(`Couldn't get user document of user ${email}`);
-                    }
-                    const notifDoc = await Cast(userDocument.optionalRightCollection, Doc);
-                    if (notifDoc instanceof Doc) {
-                        const data = await Cast(notifDoc.data, listSpec(Doc));
-                        const sharedDoc = Doc.MakeAlias(this.props.Document);
-                        if (data) {
-                            data.push(sharedDoc);
-                        } else {
-                            notifDoc.data = new List([sharedDoc]);
+                    if (userDocument) {
+                        const notifDoc = await Cast(userDocument.optionalRightCollection, Doc);
+                        if (notifDoc) {
+                            const data = await Cast(notifDoc.data, listSpec(Doc));
+                            const sharedDoc = Doc.MakeAlias(this.props.Document);
+                            if (data) {
+                                data.push(sharedDoc);
+                            } else {
+                                notifDoc.data = new List([sharedDoc]);
+                            }
                         }
                     }
-                }, icon: "male"
-            }));
+                },
+                icon: "male"
+            } as ContextMenuProps));
+            cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
         } catch {
 
         }
         runInAction(() => {
-            cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
             if (!ClientUtils.RELEASE) {
                 let setWriteMode = (mode: DocServer.WriteMode) => {
                     DocServer.AclsMode = mode;
@@ -651,19 +626,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     isSelected = () => SelectionManager.IsSelected(this);
-    @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
-    @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
-    @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
-    @computed get contents() {
-        return (<DocumentContentsView {...this.props}
-            ChromeHeight={this.chromeHeight}
-            isSelected={this.isSelected}
-            select={this.select}
-            onClick={this.onClickHandler}
-            layoutKey={"layout"}
-            DataDoc={this.props.DataDoc} />);
-    }
+    select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
     chromeHeight = () => {
         let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
@@ -715,6 +678,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}
                 />
             </div>);
+        const contents = (<DocumentContentsView {...this.props}
+            ChromeHeight={this.chromeHeight}
+            isSelected={this.isSelected}
+            select={this.select}
+            onClick={this.onClickHandler}
+            layoutKey={"layout"}
+            DataDoc={this.props.DataDoc} />);
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -738,15 +708,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 {!showTitle && !showCaption ?
                     this.Document.searchFields ?
                         (<div className="documentView-searchWrapper">
-                            {this.contents}
+                            {contents}
                             {searchHighlight}
                         </div>)
                         :
-                        this.contents
+                        contents
                     :
                     <div className="documentView-styleWrapper" >
                         <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? "calc(100% - 29px)" : "100%", top: showTextTitle ? "29px" : undefined }}>
-                            {this.contents}
+                            {contents}
                         </div>
                         {titleView}
                         {captionView}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3883886e8..ab0412c37 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -297,8 +297,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
-                if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+                if (de.mods === "AltKey") {
+                    if (draggedDoc.data instanceof RichTextField) {
+                        Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data);
+                        e.stopPropagation();
+                    }
+                } else {
                     draggedDoc.isTemplate = true;
                     if (typeof (draggedDoc.layout) === "string") {
                         let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bbcc296e6..3cb64aa40 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -87,26 +87,14 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
-            de.data.droppedDocuments.forEach(action((drop: Doc) => {
-                if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
-                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
-                    e.stopPropagation();
-                } else if (de.mods === "MetaKey") {
-                    if (this.extensionDoc !== this.dataDoc) {
-                        let layout = StrCast(drop.backgroundLayout);
-                        if (layout.indexOf(ImageBox.name) !== -1) {
-                            let imgData = this.extensionDoc.Alternates;
-                            if (!imgData) {
-                                Doc.GetProto(this.extensionDoc).Alternates = new List([]);
-                            }
-                            let imgList = Cast(this.extensionDoc.Alternates, listSpec(Doc), [] as any[]);
-                            imgList && imgList.push(drop);
-                            e.stopPropagation();
-                        }
-                    }
-                }
+            if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) {
+                Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url);
+                e.stopPropagation();
+            }
+            de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => {
+                Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop);
+                e.stopPropagation();
             }));
-            // de.data.removeDocument()  bcz: need to implement
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 6f7936d5c71bf3c802d73f47b19abe96c6d61848 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 20 Sep 2019 15:11:30 -0400
Subject: simplified script execution api a little.  fixed dataDoc() related
 stuff in various Box's.  fixed some template stuff.

---
 src/client/util/Scripting.ts                       |  8 ++--
 src/client/util/SelectionManager.ts                |  4 ++
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/ScriptingRepl.tsx                 | 20 ++++----
 src/client/views/TemplateMenu.tsx                  | 11 ++++-
 .../views/collections/CollectionBaseView.tsx       |  4 +-
 .../views/collections/CollectionSchemaCells.tsx    | 10 ++--
 src/client/views/collections/CollectionSubView.tsx | 40 ++++++----------
 .../views/collections/CollectionTreeView.tsx       | 13 +----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 56 +++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx | 14 ++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 29 +++++++----
 src/client/views/nodes/DragBox.tsx                 |  8 ++--
 src/client/views/nodes/FormattedTextBox.tsx        |  7 ++-
 src/client/views/nodes/ImageBox.tsx                |  6 +--
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 22 ++++-----
 src/client/views/nodes/VideoBox.tsx                |  2 +-
 src/client/views/pdf/PDFAnnotationLayer.scss       |  6 ---
 src/client/views/pdf/PDFAnnotationLayer.tsx        | 21 --------
 src/client/views/pdf/PDFViewer.tsx                 | 17 ++-----
 src/debug/Repl.tsx                                 |  8 +---
 src/new_fields/Doc.ts                              |  6 +--
 src/new_fields/ScriptField.ts                      |  5 +-
 25 files changed, 136 insertions(+), 189 deletions(-)
 delete mode 100644 src/client/views/pdf/PDFAnnotationLayer.scss
 delete mode 100644 src/client/views/pdf/PDFAnnotationLayer.tsx

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

diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 1d0916ac0..ff4451824 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -19,6 +19,7 @@ export interface ScriptSucccess {
 export interface ScriptError {
     success: false;
     error: any;
+    result: any;
 }
 
 export type ScriptResult = ScriptSucccess | ScriptError;
@@ -27,7 +28,7 @@ export interface CompiledScript {
     readonly compiled: true;
     readonly originalScript: string;
     readonly options: Readonly<ScriptOptions>;
-    run(args?: { [name: string]: any }): ScriptResult;
+    run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult;
 }
 
 export interface CompileError {
@@ -100,7 +101,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
     // let params: any[] = [Docs, ...fieldTypes];
     let compiledFunction = new Function(...paramNames, `return ${script}`);
     let { capturedVariables = {} } = options;
-    let run = (args: { [name: string]: any } = {}): ScriptResult => {
+    let run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
         let argsArray: any[] = [];
         for (let name of customParams) {
             if (name === "this") {
@@ -127,7 +128,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
             if (batch) {
                 batch.end();
             }
-            return { success: false, error };
+            onError && onError(error);
+            return { success: false, error, result: errorVal };
         }
     };
     return { compiled: true, run, originalScript, options };
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 9efef888d..4c97a1056 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -24,6 +24,10 @@ export namespace SelectionManager {
                 manager.SelectedDocuments.push(docView);
                 // console.log(manager.SelectedDocuments);
                 docView.props.whenActiveChanged(true);
+            } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
+                manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false));
+                manager.SelectedDocuments = [docView];
+                FormattedTextBox.InputBoxOverlay = undefined;
             }
         }
         @action
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 6c29a2dc7..b482e3298 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -339,7 +339,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
 
         let templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template =>
-            templates.set(template, this.props.views.reduce((checked, doc) => checked || (doc.props.Document["show" + template.Name] ? true : false), false as boolean)));
+            templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean)));
 
         return (<div className="documentButtonBar">
             <div className="linkButtonWrapper">
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index e05195ca0..1eb380e0b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -135,19 +135,17 @@ export class ScriptingRepl extends React.Component {
                     this.commands.push({ command: this.commandString, result: script.errors });
                     return;
                 }
-                const result = script.run({ args: this.args });
-                if (!result.success) {
-                    this.commands.push({ command: this.commandString, result: result.error.toString() });
-                    return;
-                }
-                this.commands.push({ command: this.commandString, result: result.result });
-                this.commandsHistory.push(this.commandString);
+                const result = script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() }));
+                if (result.success) {
+                    this.commands.push({ command: this.commandString, result: result.result });
+                    this.commandsHistory.push(this.commandString);
 
-                this.maybeScrollToBottom();
+                    this.maybeScrollToBottom();
 
-                this.commandString = "";
-                this.commandBuffer = "";
-                this.historyIndex = -1;
+                    this.commandString = "";
+                    this.commandBuffer = "";
+                    this.historyIndex = -1;
+                }
                 break;
             }
             case "ArrowUp": {
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index bfb8168e4..e4ef8313d 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -98,7 +98,14 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @undoBatch
     @action
     clearTemplates = (event: React.MouseEvent) => {
-        Templates.TemplateList.map(template => this.props.docs.map(d => d.Document["show" + template.Name] = undefined));
+        Templates.TemplateList.forEach(template => this.props.docs.forEach(d => d.Document["show" + template.Name] = undefined));
+        ["backgroundColor", "borderRounding", "width", "height"].forEach(field => this.props.docs.forEach(d => {
+            if (d.Document.isTemplate && d.props.DataDoc) {
+                d.Document[field] = undefined;
+            } else if (d.Document["default" + field[0].toUpperCase() + field.slice(1)] !== undefined) {
+                d.Document[field] = Doc.GetProto(d.Document)[field] = undefined;
+            }
+        }));
     }
 
     @action
@@ -128,7 +135,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
                 <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
-                    {/* <button onClick={this.clearTemplates}>Clear</button> */}
+                    {<button onClick={this.clearTemplates}>Restore Defaults</button>}
                 </ul>
             </div>
         );
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 0399371ff..56d12bd84 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -79,7 +79,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         }
     }
 
-    @computed get dataDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc ? this.props.DataDoc : this.props.Document : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+    @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
     @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; }
 
     active = (): boolean => {
@@ -94,7 +94,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         this.props.whenActiveChanged(isActive);
     }
 
-    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
 
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 0306d415c..4dac27e60 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -238,13 +238,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                                 return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
                             }}
                             OnFillDown={async (value: string) => {
-                                let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
-                                if (!script.compiled) {
-                                    return;
+                                const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+                                if (script.compiled) {
+                                    DocListCast(this.props.Document[this.props.fieldKey]).
+                                        forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, script.run));
                                 }
-                                const run = script.run;
-                                const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
-                                val && val.forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, run));
                             }}
                         />
                     </div >
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 804bfa2b2..774e6b1b9 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -40,6 +40,8 @@ export interface SubCollectionViewProps extends CollectionViewProps {
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
     class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
+        private _childLayoutDisposer?: IReactionDisposer;
+
         protected createDropTarget = (ele: HTMLDivElement) => {
             this.dropDisposer && this.dropDisposer();
             if (ele) {
@@ -50,8 +52,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this.createDropTarget(ele);
         }
 
-        _childLayoutDisposer?: IReactionDisposer;
-
         componentDidMount() {
             this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
                 async (args) => args[1] instanceof Doc &&
@@ -62,35 +62,25 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this._childLayoutDisposer && this._childLayoutDisposer();
         }
 
-        @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+        // The data field for rendeing this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
+        // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
+        // to its children which may be templates.
+        // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise.
+        @computed get dataField() {
+            return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey];
+        }
 
 
         get childLayoutPairs() {
             return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! }));
         }
-        get childDocs() {
-            //TODO tfs: This might not be what we want?
-            //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
-            let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
-            if (viewSpecScript) {
-                let script = viewSpecScript.script;
-                docs = docs.filter(d => {
-                    let res = script.run({ doc: d });
-                    if (res.success) {
-                        return res.result;
-                    }
-                    else {
-                        console.log(res.error);
-                    }
-                });
-            }
-            return docs;
-        }
         get childDocList() {
-            //TODO tfs: This might not be what we want?
-            //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            return Cast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc));
+            return Cast(this.dataField, listSpec(Doc));
+        }
+        get childDocs() {
+            let docs = DocListCast(this.dataField);
+            const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+            return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
         }
 
         @action
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 08d87c7b2..e5313f68c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -425,18 +425,9 @@ class TreeView extends React.Component<TreeViewProps> {
         preventTreeViewOpen: boolean,
         renderedIds: string[]
     ) {
-        let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
+        const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
-            let script = viewSpecScript.script;
-            docs = docs.filter(d => {
-                let res = script.run({ doc: d });
-                if (res.success) {
-                    return res.result;
-                }
-                else {
-                    console.log(res.error);
-                }
-            });
+            docs = docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
         }
 
         let ascending = Cast(containingCollection.sortAscending, "boolean", null);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7383c5551..36e62842c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -156,6 +156,7 @@ export namespace PivotView {
                         y={pos.y}
                         width={pos.width}
                         height={pos.height}
+                        transition={"transform 1s"}
                         jitterRotation={NumCast(target.props.Document.jitterRotation)}
                         {...target.getChildDocumentViewProps(doc)}
                     />,
@@ -183,11 +184,9 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
-    private _inkKey = "ink"; // the document key used to store ink annotation strokes
     private get _pwidth() { return this.props.PanelWidth(); }
     private get _pheight() { return this.props.PanelHeight(); }
-
-    get parentScaling() {
+    private get parentScaling() {
         return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
     }
 
@@ -264,7 +263,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed get fieldExtensionDoc() {
-        return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
+        return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
     }
 
     intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -700,10 +699,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    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);
-        return !result.success ? {} : result.result !== undefined ? result.result :
-            { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") };
+    getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } {
+        const script = this.Document.arrangeScript;
+        const result = script && script.script.run(params, console.log);
+        if (result && result.success) {
+            return { ...result, transition: "transform 1s" };
+        }
+        return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") };
     }
 
     viewDefsToJSX = (views: any[]) => {
@@ -745,12 +747,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (this.Document.usePivotLayout) return PivotView.elements(this);
         let curPage = FieldValue(this.Document.curPage, -1);
         const initScript = this.Document.arrangeInit;
-        const script = this.Document.arrangeScript;
         let state: any = undefined;
         let pairs = this.childLayoutPairs;
         let elements: ViewDefResult[] = [];
         if (initScript) {
-            const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document });
+            const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log);
             if (initResult.success) {
                 const result = initResult.result;
                 const { state: scriptState, views } = result;
@@ -760,23 +761,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
         let docviews = pairs.reduce((prev, pair) => {
             var page = NumCast(pair.layout.page, -1);
-            if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
-                let minim = BoolCast(pair.layout.isMinimized);
-                if (minim === undefined || !minim) {
-                    const pos = script ? this.getCalculatedPositions(script, { doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }) :
-                        { x: Cast(pair.layout.x, "number"), y: Cast(pair.layout.y, "number"), z: Cast(pair.layout.z, "number"), width: Cast(pair.layout.width, "number"), height: Cast(pair.layout.height, "number") };
-                    state = pos.state === undefined ? state : pos.state;
-                    if (pair.layout && !(pair.data instanceof Promise)) {
-                        prev.push({
-                            ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}
-                                ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
-                                jitterRotation={NumCast(this.props.Document.jitterRotation)}
-                                x={script ? pos.x : undefined} y={script ? pos.y : undefined}
-                                width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
-                            bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) }
-                        });
-                    }
-                }
+            if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) {
+                const pos = this.getCalculatedPositions({ doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state });
+                state = pos.state === undefined ? state : pos.state;
+                prev.push({
+                    ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}
+                        ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
+                        jitterRotation={NumCast(this.props.Document.jitterRotation)}
+                        transition={pos.transition} x={pos.x} y={pos.y} width={pos.width} height={pos.height}
+                        {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+                    bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 }
+                });
             }
             return prev;
         }, elements);
@@ -838,7 +833,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     analyzeStrokes = async () => {
-        let data = Cast(this.fieldExtensionDoc[this._inkKey], InkField);
+        let data = Cast(this.fieldExtensionDoc.ink, InkField);
         if (data) {
             CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData);
         }
@@ -932,12 +927,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     render() {
+        // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x;
         this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y;
         this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x);
         this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y);
+        // if fieldExt is set, then children will be stored in the extension document for the fieldKey. 
+        // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
+        Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
         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}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 18d0fea8c..bbea4a555 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -228,18 +228,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
     }
 
-    get ink() {
-        let container = this.props.container.props.Document;
-        let containerKey = this.props.container.props.fieldKey;
-        let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true");
-        return Cast(extensionDoc.ink, InkField);
+    get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
+        let cprops = this.props.container.props;
+        return Cast(Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink, InkField);
     }
 
     set ink(value: InkField | undefined) {
-        let container = Doc.GetProto(this.props.container.props.Document);
-        let containerKey = this.props.container.props.fieldKey;
-        let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true");
-        extensionDoc.ink = value;
+        let cprops = this.props.container.props;
+        Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink = value;
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d05e39e2b..9685f9bca 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,6 +17,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     width?: number;
     height?: number;
     jitterRotation: number;
+    transition?: string;
 }
 export const positionSchema = createSchema({
     zIndex: "number",
@@ -98,7 +99,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable _animPos: number[] | undefined = undefined;
 
     render() {
-        const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -110,7 +110,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                                         StrCast(this.layoutDoc.boxShadow, ""),
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
-                    transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : hasPosition ? "transform 1s" : StrCast(this.layoutDoc.transition),
+                    transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : StrCast(this.layoutDoc.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 584b5d024..d8cfff973 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -192,7 +192,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.DeselectAll();
                 Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler && this.onClickHandler.script) {
-                this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document });
+                this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
             } else if (this.Document.isButton) {
                 SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
                 this.buttonClick(e.altKey, e.ctrlKey);
@@ -569,32 +569,45 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         });
     }
 
+
+    // the document containing the view layout information - will be the Document itself unless the Document has
+    // a layout field.  In that case, all layout information comes from there unless overriden by Document
+    get layoutDoc(): Document {
+        return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document);
+    }
+
+    // does Document set a layout prop 
+    setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)];
+    // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise.
+    getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
+    getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
+
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
     chromeHeight = () => {
         let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
         let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.Document.showTitle);
-        return (showTitle ? 25 : 0) + 1;// bcz: why 8??
+        return (showTitle ? 25 : 0) + 1;
     }
 
     render() {
         const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
         const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
-        const colorSet = this.Document.backgroundColor !== this.Document.defaultBackgroundColor;
+        const colorSet = this.setsLayoutProp("backgroundColor");
         const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
         const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ?
-            this.props.backgroundColor(this.Document) || StrCast(this.Document.backgroundColor) :
-            ruleColor && !colorSet ? ruleColor : StrCast(this.Document.backgroundColor) || this.props.backgroundColor(this.Document);
+            this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
+            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
 
         const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%";
         const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
-        const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.Document.showTitle;
-        const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.Document.showCaption;
+        const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
+        const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
         const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        const borderRounding = this.Document.borderRounding || ruleRounding;
+        const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
         const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
         const searchHighlight = (!this.Document.searchFields ? (null) :
             <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx
index 2d1a98df2..6c3db18c4 100644
--- a/src/client/views/nodes/DragBox.tsx
+++ b/src/client/views/nodes/DragBox.tsx
@@ -51,11 +51,9 @@ export class DragBox extends DocComponent<FieldViewProps, DragDocument>(DragDocu
             const onDragStart = this.Document.onDragStart;
             e.stopPropagation();
             e.preventDefault();
-            let res = onDragStart ? onDragStart.script.run({ this: this.props.Document }) : undefined;
-            let doc = res !== undefined && res.success ?
-                res.result as Doc :
-                Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" });
-            doc && DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+            let res = onDragStart && onDragStart.script.run({ this: this.props.Document }).result;
+            let doc = (res as Doc) ||  Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" });
+            DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
         }
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 031929bbd..eb4718581 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -142,10 +142,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
 
-    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); }
-
-    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     // this should be internal to prosemirror, but is needed
     // here to make sure that footnote view nodes in the overlay editor
@@ -641,7 +640,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
                 let annotations = DocListCast(region.annotations);
                 annotations.forEach(anno => anno.target = this.props.Document);
-                let fieldExtDoc = Doc.resolvedFieldDataDoc(doc, "data", "true");
+                let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data");
                 let targetAnnotations = DocListCast(fieldExtDoc.annotations);
                 if (targetAnnotations) {
                     targetAnnotations.push(region);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3cb64aa40..624593245 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -63,9 +63,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     @observable private _isOpen: boolean = false;
     private dropDisposer?: DragManager.DragDropDisposer;
 
+    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
-    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
-
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         if (this.dropDisposer) {
@@ -81,8 +81,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         console.log("IMPLEMENT ME PLEASE");
     }
 
-    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "Alternates"); }
-
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 8fcd44f46..3a9318469 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -76,7 +76,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         } else if (type === "script") {
             field = new ScriptField(script);
         } else {
-            let res = script.run({ this: target });
+            let res = script.run({ this: target }, console.log);
             if (!res.success) return false;
             field = res.result;
         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c8534ed0d..37bf6dbb7 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -31,11 +31,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @observable private _alt = false;
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
 
-    @computed get containingCollectionDocument() { return this.props.ContainingCollectionDoc; }
-    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
-
-    @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); }
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
@@ -96,7 +94,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     @action
     setPanY = (y: number) => {
-        this.containingCollectionDocument && (this.containingCollectionDocument.panY = y);
+        this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.panY = y);
     }
 
     @action
@@ -113,7 +111,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     private resetFilters = () => {
         this._keyValue = this._valueValue = "";
-        this._scriptValue = "return true";
+        this._scriptValue = "";
         this._keyRef.current && (this._keyRef.current.value = "");
         this._valueRef.current && (this._valueRef.current.value = "");
         this._scriptRef.current && (this._scriptRef.current.value = "");
@@ -127,7 +125,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         return !this.props.active() ? (null) :
             (<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>
                 <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings"
-                    style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}>
+                    style={{ marginTop: `${this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.panY) : 0}px` }}>
                     <div className="pdfBox-settingsButton-arrow"
                         style={{
                             borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
@@ -179,9 +177,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     @action
     onScroll = (e: React.UIEvent<HTMLDivElement>) => {
-        if (e.currentTarget && this.containingCollectionDocument) {
-            this.containingCollectionDocument.panTransformType = "None";
-            this.containingCollectionDocument.panY = e.currentTarget.scrollTop;
+        if (e.currentTarget && this.props.ContainingCollectionDoc) {
+            this.props.ContainingCollectionDoc.panTransformType = "None";
+            this.props.ContainingCollectionDoc.panY = e.currentTarget.scrollTop;
         }
     }
 
@@ -193,14 +191,14 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
             <div className={classname}
                 onScroll={this.onScroll}
-                style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}
+                style={{ marginTop: `${this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.panY) : 0}px` }}
                 ref={this._mainCont}>
                 <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + (NumCast(this.props.Document.nativeHeight) - NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.scale, 1)), width: "100%" }} />
                 <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)}
                     Document={this.props.Document} DataDoc={this.props.DataDoc}
                     addDocTab={this.props.addDocTab} setPanY={this.setPanY}
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
-                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} />
+                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} />
                 {this.settingsPanel()}
             </div>);
     }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index aa6be56bb..b7d9a1eab 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -257,7 +257,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     }
 
-    @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     @computed get youtubeContent() {
         this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
diff --git a/src/client/views/pdf/PDFAnnotationLayer.scss b/src/client/views/pdf/PDFAnnotationLayer.scss
deleted file mode 100644
index 733533007..000000000
--- a/src/client/views/pdf/PDFAnnotationLayer.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.pdfAnnotationLayer-cont {
-    width:100%;
-    height:100%;
-    position:relative;
-    top:-200%;
-}
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFAnnotationLayer.tsx b/src/client/views/pdf/PDFAnnotationLayer.tsx
deleted file mode 100644
index 4f267a5c0..000000000
--- a/src/client/views/pdf/PDFAnnotationLayer.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import "./PDFAnnotationLayer.scss";
-
-interface IAnnotationProps {
-
-}
-
-@observer
-export class PDFAnnotationLayer extends React.Component {
-    onPointerDown = (e: React.PointerEvent) => {
-        if (e.ctrlKey) {
-            console.log("annotating");
-            e.stopPropagation();
-        }
-    }
-
-    render() {
-        return <div className="pdfAnnotationLayer-cont" onPointerDown={this.onPointerDown} />;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 19ef713c2..4afed0b95 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -77,18 +77,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     @computed get allAnnotations() {
-        let annotations = DocListCast(this.props.fieldExtensionDoc.annotations);
-        return annotations.filter(anno => {
-            let run = this._script.run({ this: anno });
-            return run.success ? run.result : true;
-        });
+        return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
+            anno => this._script.run({ this: anno }, console.log, true).result);
     }
 
     @computed get nonDocAnnotations() {
-        return this._annotations.filter(anno => {
-            let run = this._script.run({ this: anno });
-            return run.success ? run.result : true;
-        });
+        return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result);
     }
 
     componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages();
@@ -114,10 +108,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 if (this._script.originalScript !== oldScript) {
                     this.Index = -1;
                 }
-                annos.forEach(d => {
-                    let run = this._script.run(d);
-                    d.opacity = !run.success || run.result ? 1 : 0;
-                });
+                annos.forEach(d => d.opacity = this._script.run({ this: d }, console.log, 1).result ? 1 : 0);
             }),
             { fireImmediately: true }
         );
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index 4f4db13d2..fd6b47ff0 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -28,12 +28,8 @@ class Repl extends React.Component {
             if (!script.compiled) {
                 this.executedCommands.push({ command: this.text, result: "Compile Error" });
             } else {
-                const result = script.run({ makeInterface });
-                if (result.success) {
-                    this.executedCommands.push({ command: this.text, result: result.result });
-                } else {
-                    this.executedCommands.push({ command: this.text, result: result.error.message || result.error });
-                }
+                const result = script.run({ makeInterface }, e => this.executedCommands.push({ command: this.text, result: e.message || e }));
+                result.success && this.executedCommands.push({ command: this.text, result: result.result });
             }
             this.text = "";
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0859cf41a..b875e7896 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -382,13 +382,13 @@ export namespace Doc {
     }
 
     //
-    // Resolves a reference to a field by returning 'doc' if field extension is specified,
+    // Resolves a reference to a field by returning 'doc' if no field extension is specified,
     // otherwise, it returns the extension document stored in doc.<fieldKey>_ext.
     // This mechanism allows any fields to be extended with an extension document that can
     // be used to capture field-specific metadata.  For example, an image field can be extended
     // to store annotations, ink, and other data.
     //
-    export function resolvedFieldDataDoc(doc: Doc, fieldKey: string, fieldExt: string) {
+    export function fieldExtensionDoc(doc: Doc, fieldKey: string, fieldExt: string = "yes") {
         return fieldExt && doc[fieldKey + "_ext"] instanceof Doc ? doc[fieldKey + "_ext"] as Doc : doc;
     }
 
@@ -477,7 +477,7 @@ export namespace Doc {
         let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined;
         if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
             Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey);
-            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+            let fieldExtensionDoc = Doc.fieldExtensionDoc(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 };
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 8f10766df..cdc9871a8 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -125,10 +125,7 @@ export class ScriptField extends ObjectField {
 @Deserializable("computed", deserializeScript)
 export class ComputedField extends ScriptField {
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
-    value = computedFn((doc: Doc) => {
-        const val = this.script.run({ this: doc });
-        return val.success ? val.result : undefined;
-    });
+    value = computedFn((doc: Doc) => this.script.run({ this: doc }, console.log).result);
     public static MakeScript(script: string, params: object = {}, ) {
         let compiled = ScriptField.CompileScript(script, params, false);
         return compiled.compiled ? new ComputedField(compiled) : undefined;
-- 
cgit v1.2.3-70-g09d2


From b69106994f5450e2ac2c515f28dacceb2e13b78b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 25 Sep 2019 00:16:55 -0400
Subject: refactored collection view to be cleaner and more modular for
 supporting things like PivotView. fixed pivot view selection bug.

---
 src/Utils.ts                                       |  15 +
 src/client/util/DocumentManager.ts                 |   9 +-
 src/client/util/SelectionManager.ts                |  16 -
 .../views/collections/CollectionBaseView.tsx       |   6 +-
 src/client/views/collections/CollectionView.tsx    |   9 +-
 .../views/collections/CollectionViewChromes.tsx    |   3 +-
 .../CollectionFreeFormLayoutEngines.tsx            | 119 +++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 567 ++++++---------------
 .../collections/collectionFreeForm/MarqueeView.tsx |   5 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  12 +-
 src/client/views/nodes/FormattedTextBox.scss       |  20 +-
 src/client/views/nodes/PresBox.tsx                 |   6 +-
 src/new_fields/Doc.ts                              |  13 +
 13 files changed, 340 insertions(+), 460 deletions(-)
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 65eb3cffd..4fac53c7d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -235,6 +235,21 @@ export function timenow() {
     return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
 }
 
+export function aggregateBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
+    return 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 });
+}
+export function 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);
+}
+
 export function percent2frac(percent: string) {
     return Number(percent.substr(0, percent.length - 1)) / 100;
 }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a3c7429b9..5ade2ebb3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -188,13 +188,8 @@ export class DocumentManager {
 
     @action
     zoomIntoScale = (docDelegate: Doc, scale: number) => {
-        let doc = Doc.GetProto(docDelegate);
-
-        let docView: DocumentView | null;
-        docView = DocumentManager.Instance.getDocumentView(doc);
-        if (docView) {
-            docView.props.zoomToScale(scale);
-        }
+        let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
+        docView && docView.props.zoomToScale(scale);
     }
 
     getScaleOfDocView = (docDelegate: Doc) => {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 4c97a1056..a02a270ee 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -88,20 +88,4 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments.slice();
     }
-    export function ViewsSortedHorizontally(): DocumentView[] {
-        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
-            if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
-            if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
-            return 0;
-        });
-        return sorted;
-    }
-    export function ViewsSortedVertically(): DocumentView[] {
-        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
-            if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
-            if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
-            return 0;
-        });
-        return sorted;
-    }
 }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 56d12bd84..0168c466f 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -20,7 +20,8 @@ export enum CollectionViewType {
     Docking,
     Tree,
     Stacking,
-    Masonry
+    Masonry,
+    Pivot,
 }
 
 export namespace CollectionViewType {
@@ -32,7 +33,8 @@ export namespace CollectionViewType {
         ["docking", CollectionViewType.Docking],
         ["tree", CollectionViewType.Tree],
         ["stacking", CollectionViewType.Stacking],
-        ["masonry", CollectionViewType.Masonry]
+        ["masonry", CollectionViewType.Masonry],
+        ["pivot", CollectionViewType.Pivot]
     ]);
 
     export const valueOf = (value: string) => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5f4742834..d3072ff1e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,6 +18,7 @@ import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -59,8 +60,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
             case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
+            case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Freeform:
             default:
+                this.props.Document.freeformLayoutEngine = undefined;
                 return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
         }
         return (null);
@@ -89,7 +92,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
         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 existingVm = ContextMenu.Instance.findByDescription("View Modes...");
             let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
-            subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; delete this.props.Document.usePivotLayout; }, 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: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
             }
@@ -103,10 +106,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
                 }, icon: "ellipsis-v"
             });
             subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
+            subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, 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) });
-                    subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true });
+                    subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
                     break;
                 }
             }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 7510b86a0..47b300efc 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -262,7 +262,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @observable private pivotKeyDisplay = this.pivotKey;
     getPivotInput = () => {
-        if (!this.document.usePivotLayout) {
+        if (StrCast(this.document.freeformLayoutEngine) !== "pivot") {
             return (null);
         }
         return (<input className="collectionViewBaseChrome-viewSpecsInput"
@@ -398,6 +398,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
                             <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Pivot View</option>
                         </select>
                         <div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
                             <input className="collectionViewBaseChrome-viewSpecsInput"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
new file mode 100644
index 000000000..21855b168
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -0,0 +1,119 @@
+import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
+import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
+import { ScriptBox } from "../../ScriptBox";
+import { CompileScript } from "../../../util/Scripting";
+import { ScriptField } from "../../../../new_fields/ScriptField";
+import { OverlayView, OverlayElementOptions } from "../../OverlayView";
+import { emptyFunction } from "../../../../Utils";
+import React = require("react");
+
+interface PivotData {
+    type: string;
+    text: string;
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    fontSize: number;
+}
+
+export interface ViewDefBounds {
+    x: number;
+    y: number;
+    z?: number;
+    width: number;
+    height: number;
+    transition?: string;
+}
+
+export interface ViewDefResult {
+    ele: JSX.Element;
+    bounds?: ViewDefBounds;
+}
+
+export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
+    let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+    const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
+    const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
+
+    for (const doc of childDocs) {
+        const val = doc[StrCast(pivotDoc.pivotField, "title")];
+        if (val) {
+            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
+            pivotColumnGroups.get(val)!.push(doc);
+        }
+    }
+
+    const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
+    const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
+    const docMap = new Map<Doc, ViewDefBounds>();
+    const groupNames: PivotData[] = [];
+
+    let x = 0;
+    pivotColumnGroups.forEach((val, key) => {
+        let y = 0;
+        let xCount = 0;
+        groupNames.push({
+            type: "text",
+            text: String(key),
+            x,
+            y: pivotAxisWidth + 50,
+            width: pivotAxisWidth * 1.25 * numCols,
+            height: 100,
+            fontSize: NumCast(pivotDoc.pivotFontSize, 10)
+        });
+        for (const doc of val) {
+            docMap.set(doc, {
+                x: x + xCount * pivotAxisWidth * 1.25,
+                y: -y,
+                width: pivotAxisWidth,
+                height: doc.nativeWidth ? (NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
+            });
+            xCount++;
+            if (xCount >= numCols) {
+                xCount = 0;
+                y += pivotAxisWidth * 1.25;
+            }
+        }
+        x += pivotAxisWidth * 1.25 * (numCols + 1);
+    });
+
+    childPairs.map(pair => {
+        let defaultPosition = {
+            x: NumCast(pair.layout.x),
+            y: NumCast(pair.layout.y),
+            z: NumCast(pair.layout.z),
+            width: NumCast(pair.layout.width),
+            height: NumCast(pair.layout.height)
+        };
+        const pos = docMap.get(pair.layout) || defaultPosition;
+        layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
+    });
+    return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
+};
+
+
+
+export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
+    return () => {
+        let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+            let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
+            const scriptField = Cast(doc[key], ScriptField);
+            // tslint:disable-next-line: no-unnecessary-callback-wrapper
+            let scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
+                onCancel={() => overlayDisposer()}  // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
+                onSave={(text, onError) => {
+                    const script = CompileScript(text, { params, requiredType, typecheck: false });
+                    if (!script.compiled) {
+                        onError(script.errors.map(error => error.messageText).join("\n"));
+                    } else {
+                        doc[key] = new ScriptField(script);
+                        overlayDisposer();
+                    }
+                }} />;
+            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}");
+    };
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 438529596..af84a1d73 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,23 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, reaction, trace, ObservableMap, untracked } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, 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, PromiseValue, DateCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
+import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { DocumentType } from "../../../documents/DocumentTypes";
 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";
@@ -26,20 +27,17 @@ import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView";
+import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
-import { OverlayElementOptions, OverlayView } from "../../OverlayView";
 import PDFMenu from "../../pdf/PDFMenu";
-import { ScriptBox } from "../../ScriptBox";
 import { CollectionSubView } from "../CollectionSubView";
+import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { DocServer } from "../../../DocServer";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -51,33 +49,10 @@ export const panZoomSchema = createSchema({
     arrangeInit: ScriptField,
     useClusters: "boolean",
     isRuleProvider: "boolean",
-    fitToBox: "boolean"
+    fitToBox: "boolean",
+    panTransformType: "string"
 });
 
-export interface ViewDefBounds {
-    x: number;
-    y: number;
-    z?: number;
-    width: number;
-    height: number;
-    transition?: string;
-}
-
-interface PivotData {
-    type: string;
-    text: string;
-    x: number;
-    y: number;
-    width: number;
-    height: number;
-    fontSize: number;
-}
-
-export interface ViewDefResult {
-    ele: JSX.Element;
-    bounds?: ViewDefBounds;
-}
-
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
 const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
 
@@ -85,49 +60,25 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
-    private get _pwidth() { return this.props.PanelWidth(); }
-    private get _pheight() { return this.props.PanelHeight(); }
-    private get parentScaling() {
-        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 + 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)
-            };
-        }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
-        return bounds;
-    }
-
-    @computed get actualContentBounds() {
-        return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
-    }
-
-    @computed get contentBounds() {
-        let bounds = this.actualContentBounds;
-        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.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 _clusterDistance: number = 75;
+    @observable _clusterSets: (Doc[])[] = [];
+
+    @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+    @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
+    @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
+    @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+    private 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.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 easing = () => this.props.Document.panTransformType === "Ease";
+    private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
+    private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+    private zoomScaling = () => (this.fitToContent ?
+        Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
+        this.Document.scale || 1)
+        / this.parentScaling;
+    private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 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);
@@ -145,47 +96,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
-        this.props.addDocument(newBox, false);
-        this.bringToFront(newBox);
-        this.updateCluster(newBox);
-        return true;
+        let added = this.props.addDocument(newBox, false);
+        added && this.bringToFront(newBox);
+        added && this.updateCluster(newBox);
+        return added;
     }
     private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
-            SelectionManager.SelectDoc(dv!, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
+    public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+
     public getActiveDocuments = () => {
-        const curPage = FieldValue(this.Document.curPage, -1);
-        return this.childLayoutPairs.filter(pair => {
-            var page = NumCast(pair.layout!.page, -1);
-            return page === curPage || page === -1;
-        }).map(pair => pair.layout);
+        return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     }
 
     @computed get fieldExtensionDoc() {
         return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
     }
 
-    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);
-    }
-    _clusterDistance = 75;
-    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;
-        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;
+    @action
+    onDrop = (e: React.DragEvent): void => {
+        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+        super.onDrop(e, { x: pt[0], y: pt[1] });
     }
+
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -236,18 +171,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
-    tryDragCluster(e: PointerEvent) {
-        let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
-        let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
+    pickCluster(probe: number[]) {
+        return this.childLayoutPairs.map(pair => pair.layout).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;
+            return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
+                NumCast(cd.cluster) : cluster;
         }, -1);
+    }
+    tryDragCluster(e: PointerEvent) {
+        let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
         if (cluster !== -1) {
             let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster);
 
@@ -272,36 +207,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
         return false;
     }
-    @observable sets: (Doc[])[] = [];
 
     @undoBatch
-    @action
     updateClusters(useClusters: boolean) {
-        this.Document.useClusters = useClusters;
-        this.sets.length = 0;
-        this.childLayoutPairs.map(pair => pair.layout).map(c => {
-            let included = [];
-            for (let i = 0; i < this.sets.length; i++) {
-                for (let member of this.sets[i]) {
-                    if (this.boundsOverlap(c, member)) {
-                        included.push(i);
-                        break;
-                    }
-                }
-            }
-            if (included.length === 0) {
-                this.sets.push([c]);
-            } else if (included.length === 1) {
-                this.sets[included[0]].push(c);
-            } else {
-                this.sets[included[0]].push(c);
-                for (let s = 1; s < included.length; s++) {
-                    this.sets[included[0]].push(...this.sets[included[s]]);
-                    this.sets[included[s]].length = 0;
-                }
-            }
-        });
-        this.sets.map((set, i) => set.map(member => member.cluster = i));
+        this.props.Document.useClusters = useClusters;
+        this._clusterSets.length = 0;
+        this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
     }
 
     @undoBatch
@@ -309,28 +220,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     updateCluster(doc: Doc) {
         let childLayouts = this.childLayoutPairs.map(pair => pair.layout);
         if (this.props.Document.useClusters) {
-            this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
+            this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             let preferredInd = NumCast(doc.cluster);
             doc.cluster = -1;
-            this.sets.map((set, i) => set.map(member => {
-                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) {
+            this._clusterSets.map((set, i) => set.map(member => {
+                if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
                     doc.cluster = i;
                 }
             }));
-            if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+            if (doc.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
                 doc.cluster = preferredInd;
             }
-            this.sets.map((set, i) => {
+            this._clusterSets.map((set, i) => {
                 if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
                     doc.cluster = i;
                 }
             });
             if (doc.cluster === -1) {
-                doc.cluster = this.sets.length;
-                this.sets.push([doc]);
+                doc.cluster = this._clusterSets.length;
+                this._clusterSets.push([doc]);
             } else {
-                for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]);
-                this.sets[doc.cluster].push(doc);
+                for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+                this._clusterSets[doc.cluster].push(doc);
             }
         }
     }
@@ -339,13 +250,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         let clusterColor = "";
         let cluster = NumCast(doc.cluster);
         if (this.Document.useClusters) {
-            if (this.sets.length <= cluster) {
+            if (this._clusterSets.length <= cluster) {
                 setTimeout(() => this.updateCluster(doc), 0);
             } else {
                 // choose a cluster color from a palette
                 let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
                 clusterColor = colors[cluster % colors.length];
-                let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined;
+                let set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor));
                 // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
                 set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
                 set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
@@ -354,8 +265,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return clusterColor;
     }
 
+    _hitCluster = false;
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -374,7 +287,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
-            if (this.props.Document.useClusters && this.tryDragCluster(e)) {
+            if (this._hitCluster && 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);
@@ -409,8 +322,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
 
                 let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
-                let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale,
-                    this._pheight / this.zoomScaling() * cscale);
+                let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
+                    this.props.PanelHeight() / this.zoomScaling() * cscale);
                 if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
                 if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
                 if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
@@ -426,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (BoolCast(this.props.Document.lockedPosition)) return;
+        if (this.props.Document.lockedPosition) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -449,7 +362,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     setPan(panX: number, panY: number) {
-        if (!BoolCast(this.props.Document.lockedPosition)) {
+        if (!this.props.Document.lockedPosition) {
             this.props.Document.panTransformType = "None";
             var scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -459,12 +372,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }
     }
 
-    @action
-    onDrop = (e: React.DragEvent): void => {
-        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        super.onDrop(e, { x: pt[0], y: pt[1] });
-    }
-
     bringToFront = (doc: Doc, sendToBack?: boolean) => {
         if (sendToBack || doc.isBackground) {
             doc.zIndex = 0;
@@ -502,51 +409,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
         HistoryUtil.pushState(newState);
 
-        let px = this.Document.panX;
-        let py = this.Document.panY;
-        let s = this.Document.scale;
-        this.setPan(newPanX, newPanY);
+        let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-        this.props.Document.panTransformType = "Ease";
+        this.setPan(newPanX, newPanY);
+        this.Document.panTransformType = "Ease";
         this.props.focus(this.props.Document);
-        if (willZoom) {
-            this.setScaleToZoom(doc, scale);
-        }
-        console.log("Focused " + this.Document.title + " " + s);
+        willZoom && this.setScaleToZoom(doc, scale);
+
         afterFocus && setTimeout(() => {
             if (afterFocus && afterFocus()) {
-                console.log("UnFocused " + this.Document.title + " " + s);
-                this.Document.panX = px;
-                this.Document.panY = py;
-                this.Document.scale = s;
+                this.Document.panX = savedState.px;
+                this.Document.panY = savedState.py;
+                this.Document.scale = savedState.s;
+                this.Document.panTransformType = savedState.pt;
             }
         }, 1000);
     }
 
     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 = scale * PanelHeight;
-        let targetWidth = scale * panelWidth;
-
-        let maxScaleX: number = targetWidth / docWidth;
-        let maxScaleY: number = targetHeight / docHeight;
-        let maxApplicableScale = Math.min(maxScaleX, maxScaleY);
-        this.Document.scale = maxApplicableScale;
+        this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
     }
 
     zoomToScale = (scale: number) => {
         this.Document.scale = scale;
     }
 
-    getScale = () => this.Document.scale ? this.Document.scale : 1;
+    getScale = () => this.Document.scale || 1;
 
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
-        trace();
         return {
             DataDoc: childData,
             Document: childLayout,
@@ -611,215 +501,109 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     viewDefsToJSX = (views: any[]) => {
-        let elements: ViewDefResult[] = [];
-        if (Array.isArray(views)) {
-            elements = views.reduce<typeof elements>((prev, ele) => {
-                const jsx = this.viewDefToJSX(ele);
-                jsx && prev.push(jsx);
-                return prev;
-            }, elements);
-        }
-        return elements;
+        return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
     }
 
     private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
         if (viewDef.type === "text") {
-            const text = Cast(viewDef.text, "string");
+            const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
             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)) {
-                return undefined;
-            }
-
-            return {
-                ele: <div className="collectionFreeform-customText" style={{
-                    transform: `translate(${x}px, ${y}px)`,
-                    width, height, fontSize
-                }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
-            };
+            return [text, x, y, width, height].some(val => val === undefined) ? undefined :
+                {
+                    ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+                        {text}
+                    </div>,
+                    bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
+                };
         }
     }
 
     lookupLayout = (doc: Doc, dataDoc?: Doc) => {
         let data: any = undefined;
-        let compute = this.Document.usePivotLayout ? this.doPivotComputation.map : this._doComputation.map;
-        compute.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
+        let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+        switch (this.Document.freeformLayoutEngine) {
+            case "pivot": computedElementData = this.doPivotLayout; break;
+            default: computedElementData = this.doFreeformLayout; break;
+        }
+        computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
             if (key.layout === doc && key.data === dataDoc) {
                 data = value;
             }
         });
-        return data ? { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition } : undefined;
+        return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition };
     }
 
-    @computed get doPivotComputation() {
-        let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
-        const field = StrCast(this.props.Document.pivotField) || "title";
-        const width = NumCast(this.props.Document.pivotWidth) || 200;
-        const groups = new Map<FieldResult<Field>, Doc[]>();
-
-        for (const doc of this.childDocs) {
-            const val = doc[field];
-            if (val === undefined) continue;
-
-            const l = groups.get(val);
-            if (l) {
-                l.push(doc);
-            } else {
-                groups.set(val, [doc]);
-            }
-        }
-
-        let minSize = Infinity;
-
-        groups.forEach((val, key) => minSize = Math.min(minSize, val.length));
-
-        const numCols = NumCast(this.props.Document.pivotNumColumns) || Math.ceil(Math.sqrt(minSize));
-        const fontSize = NumCast(this.props.Document.pivotFontSize);
-
-        const docMap = new Map<Doc, ViewDefBounds>();
-        const groupNames: PivotData[] = [];
-
-        let x = 0;
-        groups.forEach((val, key) => {
-            let y = 0;
-            let xCount = 0;
-            groupNames.push({
-                type: "text",
-                text: String(key),
-                x,
-                y: width + 50,
-                width: width * 1.25 * numCols,
-                height: 100, fontSize: fontSize
-            });
-            for (const doc of val) {
-                docMap.set(doc, {
-                    x: x + xCount * width * 1.25,
-                    y: -y,
-                    width,
-                    height: width,
-                });
-                xCount++;
-                if (xCount >= numCols) {
-                    xCount = 0;
-                    y += width * 1.25;
-                }
-            }
-            x += width * 1.25 * (numCols + 1);
-        });
-
-        let elements = this.viewDefsToJSX(groupNames);
-        let pairs = this.childLayoutPairs;
-        pairs.map((pair, i) => {
-            let minim = BoolCast(pair.layout.isMinimized);
-            if (minim === undefined || !minim) {
-                let defaultPosition = (): ViewDefBounds => {
-                    return {
-                        x: NumCast(pair.layout.x),
-                        y: NumCast(pair.layout.y),
-                        z: NumCast(pair.layout.z),
-                        width: NumCast(pair.layout.width),
-                        height: NumCast(pair.layout.height),
-                        transition: "transform 1s"
-                    };
-                };
-                const pos = docMap.get(pair.layout) || defaultPosition();
-                layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
-            }
-        });
-        return { map: layoutPoolData, elements: elements };
-    };
-
+    @computed
+    get doPivotLayout() {
+        return computePivotLayout(this.props.Document, this.childDocs,
+            this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
+    }
 
     @computed
-    get _doComputation() {
+    get doFreeformLayout() {
         let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
-        let curPage = FieldValue(this.Document.curPage, -1);
-        const initScript = this.Document.arrangeInit;
-        let state: any = undefined;
-        let pairs = this.childLayoutPairs;
-        let elements: ViewDefResult[] = [];
-        if (initScript) {
-            const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log);
-            if (initResult.success) {
-                const result = initResult.result;
-                const { state: scriptState, views } = result;
-                state = scriptState;
-                elements = this.viewDefsToJSX(views);
-            }
-        }
-        pairs.map((pair, i) => {
-            var page = NumCast(pair.layout.page, -1);
-            if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) {
-                const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: pairs.map(pair => pair.layout), state });
-                state = pos.state === undefined ? state : pos.state;
-                layoutPoolData.set(pair, pos);
-            }
+        let layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
+        const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+        let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
+        let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
+
+        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
+            const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+            state = pos.state === undefined ? state : pos.state;
+            layoutPoolData.set(pair, pos);
         });
         return { map: layoutPoolData, elements: elements };
     }
 
     @computed
-    get doComputation() {
-        let dc = this.Document.usePivotLayout ? this.doPivotComputation : this._doComputation;
-        let curPage = FieldValue(this.Document.curPage, -1);
-        let pairs = this.childLayoutPairs;
-        let docviews = pairs.reduce((prev, pair) => {
-            var page = NumCast(pair.layout.page, -1);
-            if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) {
-                prev.push({
-                    ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
-                        ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
-                        jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
-                    bounds: this.lookupLayout(pair.layout, pair.data)
-                });
-            }
-            return prev;
-        }, dc.elements);
+    get doLayoutComputation() {
+        let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+        switch (this.Document.freeformLayoutEngine) {
+            case "pivot": computedElementData = this.doPivotLayout; break;
+            default: computedElementData = this.doFreeformLayout; break;
+        }
+        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+            computedElementData.elements.push({
+                ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
+                    ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
+                    jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+                bounds: this.lookupLayout(pair.layout, pair.data)
+            }));
 
-        return { map: dc.map, elements: docviews };
-    }
-    @computed.struct
-    get elements() {
-        return this.doComputation.elements;
+        return computedElementData;
     }
 
-    @computed.struct
-    get views() {
-        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);
-    }
+    @computed.struct get elements() { return this.doLayoutComputation.elements; }
+    @computed.struct get views() { 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) => {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
-    arrangeContents = async () => {
-        const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
+    layoutDocsInGrid = () => {
         UndoManager.RunInBatch(() => {
-            if (docs) {
-                let startX = this.Document.panX || 0;
-                let x = startX;
-                let y = this.Document.panY || 0;
-                let i = 0;
-                const width = Math.max(...docs.map(doc => NumCast(doc.width)));
-                const height = Math.max(...docs.map(doc => NumCast(doc.height)));
-                for (const doc of docs) {
-                    doc.x = x;
-                    doc.y = y;
-                    x += width + 20;
-                    if (++i === 6) {
-                        i = 0;
-                        x = startX;
-                        y += height + 20;
-                    }
+            const docs = DocListCast(this.Document[this.props.fieldKey]);
+            let startX = this.Document.panX || 0;
+            let x = startX;
+            let y = this.Document.panY || 0;
+            let i = 0;
+            const width = Math.max(...docs.map(doc => NumCast(doc.width)));
+            const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+            for (const doc of docs) {
+                doc.x = x;
+                doc.y = y;
+                x += width + 20;
+                if (++i === 6) {
+                    i = 0;
+                    x = startX;
+                    y += height + 20;
                 }
             }
         }, "arrange contents");
@@ -854,10 +638,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
         }
         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.fitToBox ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
+        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
-        layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
+        layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
         layoutItems.push({
@@ -905,53 +689,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         <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;
-                    }
-                    doc[key] = new ScriptField(script);
-                    overlayDisposer();
-                }} />;
-                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() {
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
-        this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x;
-        this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y;
-        this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x);
-        this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y);
+        this.props.Document.fitX = this.contentBounds && this.contentBounds.x;
+        this.props.Document.fitY = this.contentBounds && this.contentBounds.y;
+        this.props.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
+        this.props.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
         // if fieldExt is set, then children will be stored in the extension document for the fieldKey. 
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
-        const easing = () => this.props.Document.panTransformType === "Ease";
         return (
             <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
                 <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
                     addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
-                    getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+                    getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
-                        easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+                        easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
                             <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} >
                                 {this.childViews}
@@ -969,23 +723,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
 @observer
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
-    @computed get overlayView() {
-        return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
-    }
     render() {
-        return this.overlayView;
+        return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />
     }
 }
 
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
-    @computed get backgroundView() {
-        return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
-    }
     render() {
-        return this.props.Document.backgroundLayout ? this.backgroundView : (null);
+        return !this.props.Document.backgroundLayout ? (null) :
+            (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
+                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />)
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bbea4a555..c85c3e55b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -30,6 +30,7 @@ interface MarqueeViewProps {
     removeDocument: (doc: Doc) => boolean;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
+    isAnnotationOverlay: boolean;
 }
 
 @observer
@@ -297,8 +298,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 y: bounds.top,
                 panX: 0,
                 panY: 0,
-                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
-                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
+                backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+                defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
                 width: bounds.width,
                 height: bounds.height,
                 title: "a nested collection",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0d9ace473..b30055071 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -41,7 +41,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
     @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
     @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
-    @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); }
 
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
@@ -74,7 +73,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelHeight = () => this.props.PanelHeight();
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
-        .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth)
+        .scale(1 / this.contentScaling())
 
     borderRounding = () => {
         let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
@@ -100,9 +99,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @observable _animPos: number[] | undefined = undefined;
 
+    finalPanelWidh = () => { return this.dataProvider ? this.dataProvider.width : this.panelWidth(); }
+    finalPanelHeight = () => { return this.dataProvider ? this.dataProvider.height : this.panelHeight(); }
+
     render() {
-        console.log("this.props =" + this.props.dataProvider);
-        trace();
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -123,8 +123,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
                     backgroundColor={this.clusterColorFunc}
-                    PanelWidth={this.panelWidth}
-                    PanelHeight={this.panelHeight}
+                    PanelWidth={this.finalPanelWidh}
+                    PanelHeight={this.finalPanelHeight}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 435f5c055..45e516015 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -164,13 +164,13 @@ ol { counter-reset: deci1 0;}
 .upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 }
 .lower-roman-ol {counter-reset: lroman;  p { display: inline }; font-size: 14; }
 .lower-alpha-ol {counter-reset: lalpha;  p { display: inline }; font-size: 10;}
-.decimal1:before { content: counter(deci1) ")"; counter-increment: deci1; display:inline-block; min-width: 30;}
-.decimal2:before { content: counter(deci1) "." counter(deci2) ")"; counter-increment: deci2; display:inline-block; min-width: 35}
-.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ")"; counter-increment: deci3; display:inline-block; min-width: 35}
-.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ")"; counter-increment: deci4; display:inline-block; min-width: 40}
-.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ")"; counter-increment: deci5; display:inline-block; min-width: 40}
-.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ")"; counter-increment: deci6; display:inline-block; min-width: 45}
-.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ")"; counter-increment: deci7; display:inline-block; min-width: 50}
-.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ")"; counter-increment: ualph; display:inline-block; min-width: 35 }
-.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ")"; counter-increment: lroman;display:inline-block; min-width: 50 }
-.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ")"; counter-increment: lalpha; display:inline-block; min-width: 35}
+.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
+.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
+.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
+.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40}
+.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40}
+.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45}
+.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50}
+.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
+.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
+.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index e376fbddb..5afd85430 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -161,10 +161,8 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
             if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.showButton) {
                 let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null);
                 let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]);
-                if (prevScale !== undefined) {
-                    if (prevScale !== curScale) {
-                        DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
-                    }
+                if (prevScale !== undefined && prevScale !== curScale) {
+                    DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
                 }
             }
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b875e7896..1b3c8b0b0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -13,6 +13,7 @@ import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
+import { intersectRect } from "../Utils";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -614,6 +615,18 @@ export namespace Doc {
         }), 0);
     }
 
+    export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) {
+        var x2 = NumCast(doc2.x) - clusterDistance;
+        var y2 = NumCast(doc2.y) - clusterDistance;
+        var w2 = NumCast(doc2.width) + clusterDistance;
+        var h2 = NumCast(doc2.height) + clusterDistance;
+        var x = NumCast(doc.x) - clusterDistance;
+        var y = NumCast(doc.y) - clusterDistance;
+        var w = NumCast(doc.width) + clusterDistance;
+        var h = NumCast(doc.height) + clusterDistance;
+        return doc.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+    }
+
     export function isBrushedHighlightedDegree(doc: Doc) {
         if (Doc.IsHighlighted(doc)) {
             return 3;
-- 
cgit v1.2.3-70-g09d2


From f4b628c2a6810c1af51508685f12287a300d6e6f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 25 Sep 2019 22:26:54 -0400
Subject: all pdf annotations work?

---
 src/client/documents/Documents.ts                  |   7 +-
 src/client/views/DocumentButtonBar.tsx             |   5 +-
 src/client/views/MainView.tsx                      |   7 +-
 src/client/views/TemplateMenu.tsx                  |   4 +-
 .../views/collections/CollectionDockingView.tsx    |   6 +-
 .../views/collections/CollectionSchemaCells.tsx    |   6 +-
 .../views/collections/CollectionSchemaView.tsx     |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   8 +-
 .../CollectionStackingViewFieldColumn.tsx          |   6 +-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  18 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  13 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx            |   3 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   6 +-
 src/client/views/nodes/PDFBox.scss                 |   3 +
 src/client/views/nodes/PDFBox.tsx                  |  20 ++--
 src/client/views/pdf/PDFViewer.scss                |   5 +-
 src/client/views/pdf/PDFViewer.tsx                 | 132 +++++++++++++--------
 20 files changed, 159 insertions(+), 109 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ea7a3a8b6..392dca373 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -95,12 +95,13 @@ export namespace Docs {
 
     export namespace Prototypes {
 
-        type LayoutSource = { LayoutString: () => string };
+        type LayoutSource = { LayoutString: (ext?: string) => string };
         type CollectionLayoutSource = { LayoutString: (fieldStr: string, fieldExt?: string) => string };
         type CollectionViewType = [CollectionLayoutSource, string, string?];
         type PrototypeTemplate = {
             layout: {
                 view: LayoutSource,
+                ext?: string, // optional extension field for layout source
                 collectionView?: CollectionViewType
             },
             options?: Partial<DocumentOptions>
@@ -144,7 +145,7 @@ export namespace Docs {
                 options: { height: 32 }
             }],
             [DocumentType.PDF, {
-                layout: { view: PDFBox },
+                layout: { view: PDFBox, ext: anno },
                 options: { nativeWidth: 1200, curPage: 1 }
             }],
             [DocumentType.ICON, {
@@ -254,7 +255,7 @@ export namespace Docs {
             // synthesize the default options, the type and title from computed values and
             // whatever options pertain to this specific prototype
             let options = { title: title, type: type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
-            let primary = layout.view.LayoutString();
+            let primary = layout.view.LayoutString(layout.ext);
             let collectionView = layout.collectionView;
             if (collectionView) {
                 options.layout = collectionView[0].LayoutString(collectionView[1], collectionView[2]);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index b482e3298..9ca54f738 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -23,6 +23,7 @@ import React = require("react");
 import { DocumentView } from './nodes/DocumentView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import { CollectionDockingView } from './collections/CollectionDockingView';
+import { DocumentDecorations } from './DocumentDecorations';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -225,7 +226,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         return (
             <div className={"linkButtonWrapper"}>
                 <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
-                    DocumentDecorations.hasPushedHack = false;
+                    DocumentButtonBar.hasPushedHack = false;
                     this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
                 }}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
@@ -259,7 +260,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
                             window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
                         } else {
                             this.clearPullColor();
-                            DocumentDecorations.hasPulledHack = false;
+                            DocumentButtonBar.hasPulledHack = false;
                             this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
                             dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
                         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 003919866..244b217ed 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -230,7 +230,7 @@ export class MainView extends React.Component {
         } else {
             DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => {
                 field instanceof Doc ? this.openWorkspace(field) :
-                    this.createNewWorkspace(CurrentUserUtils.MainDocId)
+                    this.createNewWorkspace(CurrentUserUtils.MainDocId);
             });
         }
     }
@@ -371,8 +371,9 @@ export class MainView extends React.Component {
     }
     flyoutWidthFunc = () => this.flyoutWidth;
     addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string) => {
-        if (where === "close")
+        if (where === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
+        }
         if (doc.dockingConfig) {
             this.openWorkspace(doc);
             return true;
@@ -564,7 +565,7 @@ export class MainView extends React.Component {
         let next = () => PresBox.CurrentPresentation.next();
         let back = () => PresBox.CurrentPresentation.back();
         let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres();
-        let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document); });
+        let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document, undefined, "onRight"); });
         return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>;
     }
 
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e4ef8313d..9e5e62e03 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -117,13 +117,13 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleChrome = (): void => {
         this.props.docs.map(dv => {
-            let layout = dv.Document.layout instanceof Doc ? dv.Document.layout as Doc : dv.Document;
+            let layout = dv.Document.layout instanceof Doc ? dv.Document.layout : dv.Document;
             layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
         });
     }
 
     render() {
-        let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout as Doc : this.props.docs[0].Document;
+        let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout : this.props.docs[0].Document;
         let templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e5d652648..2d9faee6b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -603,9 +603,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     contentScaling = () => {
         if (this._document!.type === DocumentType.PDF) {
-            if (this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth))
+            if (this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) {
                 return this._panelWidth / NumCast(this._document!.nativeWidth);
-            else return this._panelHeight / NumCast(this._document!.nativeHeight);
+            } else {
+                return this._panelHeight / NumCast(this._document!.nativeHeight);
+            }
         }
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 4dac27e60..179e44266 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -34,7 +34,7 @@ export interface CellProps {
     row: number;
     col: number;
     rowProps: CellInfo;
-    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    CollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ContainingCollection: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
     fieldKey: string;
@@ -151,7 +151,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             fieldExt: "",
             ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.CollectionView.props.Document,
+            ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
             isSelected: returnFalse,
             select: emptyFunction,
             renderDepth: this.props.renderDepth + 1,
@@ -301,7 +301,7 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
     render() {
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = (e: React.PointerEvent) => {
-            (!this.props.CollectionView.props.isSelected() ? undefined :
+            (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined :
                 SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
         };
         return (
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 7bd2a1971..8d931f812 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -246,7 +246,7 @@ export interface SchemaTableProps {
     PanelHeight: () => number;
     PanelWidth: () => number;
     childDocs?: Doc[];
-    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    CollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ContainingCollectionDoc: Opt<Doc>;
     fieldKey: string;
@@ -804,7 +804,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
         csv.substring(0, csv.length - 1);
         let dbName = StrCast(this.props.Document.title);
         let res = await Gateway.Instance.PostSchema(csv, dbName);
-        if (self.props.CollectionView.props.addDocument) {
+        if (self.props.CollectionView && self.props.CollectionView.props.addDocument) {
             let schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
             if (schemaDoc) {
                 //self.props.CollectionView.props.addDocument(schemaDoc, false);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ccf131797..597f3f745 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -42,7 +42,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
     @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')); }
+    @computed get showAddAGroup() { return (this.sectionFilter && this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.ContainingCollectionDoc.chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
         return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
@@ -347,7 +347,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     onToggle = (checked: Boolean) => {
-        this.props.CollectionView.props.Document.chromeStatus = checked ? "collapsed" : "view-mode";
+        this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.chromeStatus = checked ? "collapsed" : "view-mode");
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
@@ -391,10 +391,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         style={{ width: this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                         <EditableView {...editableViewProps} />
                     </div>}
-                {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? <Switch
+                {this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.chromeStatus !== 'disabled' ? <Switch
                     onChange={this.onToggle}
                     onClick={this.onToggle}
-                    defaultChecked={this.props.CollectionView.props.Document.chromeStatus !== 'view-mode'}
+                    defaultChecked={this.props.ContainingCollectionDoc.chromeStatus !== 'view-mode'}
                     checkedChildren="edit"
                     unCheckedChildren="view"
                 /> : null}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index b3b7b40dd..240adf428 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -266,7 +266,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 style={{
                     width: (style.columnWidth) /
                         ((uniqueHeadings.length +
-                            ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+                            ((this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
                 }}>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
                     Further, it does not have a color and cannot be deleted. */}
@@ -297,7 +297,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             </div> : (null);
         for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
         return (
-            <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
+            <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
                 {headingView}
                 <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
@@ -315,7 +315,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     {this.children(this.props.docList)}
                     {singleColumn ? (null) : this.props.parent.columnDragger}
                 </div>
-                {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
+                {(this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ?
                     <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
                         style={{ width: style.columnWidth / style.numGroupColumns }}>
                         <EditableView {...newEditableViewProps} />
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 774e6b1b9..ce80526b2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
@@ -30,10 +30,11 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     chromeCollapsed: boolean;
+    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
-    CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    CollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     ruleProvider: Doc | undefined;
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 21855b168..6135f3e45 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -90,9 +90,7 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
         layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
     });
     return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
-};
-
-
+}
 
 export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
     return () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 45c021c5f..075914e29 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -50,7 +50,7 @@ export const panZoomSchema = createSchema({
     useClusters: "boolean",
     isRuleProvider: "boolean",
     fitToBox: "boolean",
-    panTransformType: "string"
+    panTransformType: "string",
 });
 
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
@@ -61,6 +61,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
     private _clusterDistance: number = 75;
+    private _hitCluster = false;
     @observable _clusterSets: (Doc[])[] = [];
 
     @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
@@ -265,7 +266,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return clusterColor;
     }
 
-    _hitCluster = false;
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble && this.props.layoutKey) {
+        if (!e.cancelBubble && !this.isAnnotationOverlay) {
             if (this._hitCluster && 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();
@@ -451,7 +451,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.CollectionView.props.Document,
+            ContainingCollectionDoc: this.props.ContainingCollectionDoc,
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
             parentActive: this.props.active,
@@ -478,7 +478,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelHeight: layoutDoc[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.CollectionView.props.Document,
+            ContainingCollectionDoc: this.props.ContainingCollectionDoc,
             focus: this.focusDocument,
             backgroundColor: returnEmptyString,
             parentActive: this.props.active,
@@ -699,10 +699,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
         return (
-            <div className={"collectionfreeformview-container"} style={{ height: this.props.layoutKey ? "100%" : this.props.PanelHeight() }} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+            <div className={"collectionfreeformview-container"} style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? "100%" : this.props.PanelHeight() }} ref={this.createDropTarget} onWheel={this.onPointerWheel}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
                 <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
-                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+                    addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} setPreviewCursor={this.props.setPreviewCursor}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
@@ -725,7 +725,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     render() {
         return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />;
     }
 }
 
@@ -734,7 +734,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
     render() {
         return !this.props.Document.backgroundLayout ? (null) :
             (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />)
+                renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c85c3e55b..689a55ec4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,6 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay: boolean;
+    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
 }
 
 @observer
@@ -44,6 +45,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @observable _visible: boolean = false;
     _commandExecuted = false;
 
+    componentDidMount() {
+        this.props.setPreviewCursor && this.props.setPreviewCursor(this.setPreviewCursor);
+    }
+
     @action
     cleanupInteractions = (all: boolean = false) => {
         if (all) {
@@ -203,11 +208,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
     }
 
+    setPreviewCursor = (x: number, y: number) => {
+        this._downX = x;
+        this._downY = y;
+        PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+    }
+
     @action
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+            this.setPreviewCursor(e.clientX, e.clientY);
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {  // why do we get a click event when the cursor have moved a big distance?
             // let's cut it off here so no one else has to deal with it.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index bcb26b4c4..cd183a984 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { random } from "animejs";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined
+    dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
     x?: number;
     y?: number;
     width?: number;
@@ -99,8 +99,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @observable _animPos: number[] | undefined = undefined;
 
-    finalPanelWidh = () => { return this.dataProvider ? this.dataProvider.width : this.panelWidth(); }
-    finalPanelHeight = () => { return this.dataProvider ? this.dataProvider.height : this.panelHeight(); }
+    finalPanelWidh = () => this.dataProvider ? this.dataProvider.width : this.panelWidth();
+    finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
 
     render() {
         trace();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e89fddd25..759c064b4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -264,7 +264,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove);
         }
-        else if (!e.cancelBubble && this.active) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) ||
+            this.props.parentActive())) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) {
                     document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 47b64e260..923dd1544 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -153,7 +153,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         }
                     });
                     return { frag: Fragment.fromArray(nodes), start: start };
-                }
+                };
                 let findLinkNode = (node: Node, editor: EditorView) => {
                     if (!node.isText) {
                         const content = findLinkFrag(node.content, editor);
@@ -162,7 +162,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     const marks = [...node.marks];
                     const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
                     return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
-                }
+                };
 
                 let start = -1;
 
@@ -748,7 +748,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let ref = editorView.domAtPos(editorView.state.selection.from);
                     let refNode = ref.node as any;
                     while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
-                    let r1 = refNode && (refNode as any).getBoundingClientRect();
+                    let r1 = refNode && refNode.getBoundingClientRect();
                     let r3 = self._ref.current!.getBoundingClientRect();
                     r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
                     return true;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index b7ff84d4a..2147292d6 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -16,6 +16,9 @@
             user-select: none; 
         }
     }
+    .collectionFreeFormView-none {
+        pointer-events: none;
+    }
 }
 
 .pdfBox-cont-interactive {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e00635408..69e438d4f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
@@ -25,7 +25,7 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 
 @observer
 export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
-    public static LayoutString() { return FieldView.LayoutString(PDFBox); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(PDFBox, "data", fieldExt); }
     private _reactionDisposer?: IReactionDisposer;
     private _keyValue: string = "";
     private _valueValue: string = "";
@@ -73,24 +73,24 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
 
     public GetPage() {
-        return this._pdfViewer!._pdfViewer.currentPageNumber;
+        return this._pdfViewer!.pdfViewer.currentPageNumber;
     }
 
     @action
     public BackPage() {
-        this._pdfViewer!._pdfViewer.scrollPageIntoView({ pageNumber: Math.max(1, this.GetPage() - 1) });
+        this._pdfViewer!.pdfViewer.scrollPageIntoView({ pageNumber: Math.max(1, this.GetPage() - 1) });
         this.props.Document.curPage = this.GetPage();
     }
 
     @action
     public GotoPage = (p: number) => {
-        this._pdfViewer!._pdfViewer.scrollPageIntoView(p);
+        this._pdfViewer!.pdfViewer.scrollPageIntoView(p);
         this.props.Document.curPage = this.GetPage();
     }
 
     @action
     public ForwardPage() {
-        this._pdfViewer!._pdfViewer.scrollPageIntoView({ pageNumber: Math.min(this._pdfViewer!._pdfViewer.pagesCount, this.GetPage() + 1) });
+        this._pdfViewer!.pdfViewer.scrollPageIntoView({ pageNumber: Math.min(this._pdfViewer!.pdfViewer.pagesCount, this.GetPage() + 1) });
         this.props.Document.curPage = this.GetPage();
     }
 
@@ -153,7 +153,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="sm" />
                 </button>
                 <button className="pdfBox-overlayButton-iconCont" key="back" title="Page Back"
-                    onPointerDown={(e) => { e.stopPropagation() }}
+                    onPointerDown={(e) => e.stopPropagation()}
                     onClick={() => this.BackPage()}
                     style={{ left: 20, top: 5, height: "30px", position: "absolute", pointerEvents: "all", display: this.props.active() ? "flex" : "none" }}>
                     <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" />
@@ -211,7 +211,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
-        let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
+        let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive");
         return (!(pdfUrl instanceof PdfField) || !this._pdf ?
             <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
             <div className={classname} onWheel={(e: React.WheelEvent) => e.stopPropagation()} onPointerDown={(e: React.PointerEvent) => {
@@ -221,11 +221,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                 }
             }}>
                 <PDFViewer {...this.props} pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded}
-                    setPdfViewer={this.setPdfViewer}
+                    setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
+                    renderDepth={this.props.renderDepth}
                     Document={this.props.Document} DataDoc={this.dataDoc}
                     addDocTab={this.props.addDocTab} GoToPage={this.GotoPage}
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                    isSelected={this.props.isSelected}
                     fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} />
                 {this.settingsPanel()}
             </div>);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index a561be94d..4ec2118ee 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -18,9 +18,8 @@
     .page {
         position: relative;
     }
-
-    .pdfViewer-text {
-        transform-origin: top left;
+    .collectionfreeformview-container {
+        pointer-events: none;
     }
 
     .pdfViewer-dragAnnotationBox {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ea5e00d73..783495e5a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,29 +1,29 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, FieldResult, WidthSym, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange } from "../../../Utils";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange, emptyFunction, returnOne } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
-import { KeyCodes } from "../../northstar/utils/KeyCodes";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
+import { Transform } from "../../util/Transform";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import Annotation from "./Annotation";
 import PDFMenu from "./PDFMenu";
 import "./PDFViewer.scss";
 import React = require("react");
-import requestPromise = require("request-promise");
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
-import { listSpec } from "../../../new_fields/Schema";
-import { Transform } from "../../util/Transform";
+import { SelectionManager } from "../../util/SelectionManager";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 
 interface IViewerProps {
@@ -33,6 +33,9 @@ interface IViewerProps {
     DataDoc?: Doc;
     fieldExtensionDoc: Doc;
     fieldKey: string;
+    fieldExt: string;
+    renderDepth: number;
+    isSelected: () => boolean;
     loaded: (nw: number, nh: number, np: number) => void;
     scrollTo: (y: number) => void;
     active: () => boolean;
@@ -42,7 +45,7 @@ interface IViewerProps {
     addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
     setPdfViewer: (view: PDFViewer) => void;
     ScreenToLocalTransform: () => Transform;
-
+    ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
 }
 
 /**
@@ -60,18 +63,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable private _marqueeWidth: number = 0;
     @observable private _marqueeHeight: number = 0;
 
+    public pdfViewer: any;
+    private _isChildActive = false;
+    private _setPreviewCursor: undefined | ((x: number, y: number) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
     private _annotationReactionDisposer?: IReactionDisposer;
     private _filterReactionDisposer?: IReactionDisposer;
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    public _pdfViewer: any;
     private _selectionText: string = "";
     private _marquee: React.RefObject<HTMLDivElement> = React.createRef();
     private _marqueeing: boolean = false;
-    private _startY: number = 0;
     private _startX: number = 0;
+    private _startY: number = 0;
+    private _downX: number = 0;
+    private _downY: number = 0;
 
     @computed get allAnnotations() {
         return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
@@ -157,22 +164,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     setupPdfJsViewer = () => {
-        document.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1);
+        document.addEventListener("pagesinit", () => this.pdfViewer.currentScaleValue = 1);
         document.addEventListener("pagerendered", () => console.log("rendered"));
         var pdfLinkService = new PDFJSViewer.PDFLinkService();
         let pdfFindController = new PDFJSViewer.PDFFindController({
             linkService: pdfLinkService,
         });
-        this._pdfViewer = new PDFJSViewer.PDFViewer({
+        this.pdfViewer = new PDFJSViewer.PDFViewer({
             container: this._mainCont.current,
             viewer: this._viewer.current,
             linkService: pdfLinkService,
             findController: pdfFindController,
             renderer: "svg"
         });
-        pdfLinkService.setViewer(this._pdfViewer);
+        pdfLinkService.setViewer(this.pdfViewer);
         pdfLinkService.setDocument(this.props.pdf, null);
-        this._pdfViewer.setDocument(this.props.pdf);
+        this.pdfViewer.setDocument(this.props.pdf);
     }
 
     @action
@@ -195,7 +202,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             annoDocs.push(annoDoc);
             annoDoc.isButton = true;
             anno.remove();
-            this.props.addDocument && this.props.addDocument(annoDoc, false);
+            // this.props.addDocument && this.props.addDocument(annoDoc, false);
             mainAnnoDoc = annoDoc;
             mainAnnoDocProto.y = annoDoc.y;
             mainAnnoDocProto = Doc.GetProto(annoDoc);
@@ -305,8 +312,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     search = (searchString: string) => {
-        if (this._pdfViewer._pageViewsReady) {
-            this._pdfViewer.findController.executeCommand('findagain', {
+        if (this.pdfViewer._pageViewsReady) {
+            this.pdfViewer.findController.executeCommand('findagain', {
                 caseSensitive: false,
                 findPrevious: undefined,
                 highlightAll: true,
@@ -316,7 +323,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         else if (this._mainCont.current) {
             let executeFind = () => {
-                this._pdfViewer.findController.executeCommand('find', {
+                this.pdfViewer.findController.executeCommand('find', {
                     caseSensitive: false,
                     findPrevious: undefined,
                     highlightAll: true,
@@ -333,7 +340,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
     onPointerDown = (e: React.PointerEvent): void => {
         // if alt+left click, drag and annotate
         if (NumCast(this.props.Document.scale, 1) !== 1) return;
-        if (!e.altKey && e.button === 0) {
+        this._marqueeing = false;
+        if (!e.altKey && e.button === 0 && this.active()) {
             PDFMenu.Instance.StartDrag = this.startDrag;
             PDFMenu.Instance.Highlight = this.highlight;
             PDFMenu.Instance.Snippet = this.createSnippet;
@@ -346,6 +354,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }
             }
             else {
+                this._downX = e.clientX;
+                this._downY = e.clientY;
                 // set marquee x and y positions to the spatially transformed position
                 if (this._mainCont.current) {
                     let boundingRect = this._mainCont.current.getBoundingClientRect();
@@ -356,15 +366,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 this._marquee.current && (this._marquee.current.style.opacity = "0.2");
                 this.receiveAnnotations([], -1);
             }
-            document.removeEventListener("pointermove", this.onSelectStart);
-            document.addEventListener("pointermove", this.onSelectStart);
+            document.removeEventListener("pointermove", this.onSelectMove);
+            document.addEventListener("pointermove", this.onSelectMove);
             document.removeEventListener("pointerup", this.onSelectEnd);
             document.addEventListener("pointerup", this.onSelectEnd);
         }
     }
 
     @action
-    onSelectStart = (e: PointerEvent): void => {
+    onSelectMove = (e: PointerEvent): void => {
         if (this._marqueeing && this._mainCont.current) {
             // transform positions and find the width and height to set the marquee to
             let boundingRect = this._mainCont.current.getBoundingClientRect();
@@ -419,7 +429,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     onSelectEnd = (e: PointerEvent): void => {
         if (this._marqueeing) {
-            this._marqueeing = false;
             if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
                 if (this._marquee.current) { // make a copy of the marquee
                     let copy = document.createElement("div");
@@ -444,23 +453,23 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
             this._marqueeHeight = this._marqueeWidth = 0;
         }
-        // else {
-        //     let sel = window.getSelection();
-        //     if (sel && sel.type === "Range") {
-        //         let selRange = sel.getRangeAt(0);
-        //         this.createTextAnnotation(sel, selRange);
-        //         PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
-        //     }
-        // }
-
-        // if (PDFMenu.Instance.Highlighting) {
-        //     this.highlight(undefined, "goldenrod");
-        // }
-        // else {
-        //     PDFMenu.Instance.StartDrag = this.startDrag;
-        //     PDFMenu.Instance.Highlight = this.highlight;
-        // }
-        document.removeEventListener("pointermove", this.onSelectStart);
+        else {
+            let sel = window.getSelection();
+            if (sel && sel.type === "Range") {
+                let selRange = sel.getRangeAt(0);
+                this.createTextAnnotation(sel, selRange);
+                PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+            }
+        }
+
+        if (PDFMenu.Instance.Highlighting) {
+            this.highlight(undefined, "goldenrod");
+        }
+        else {
+            PDFMenu.Instance.StartDrag = this.startDrag;
+            PDFMenu.Instance.Highlight = this.highlight;
+        }
+        document.removeEventListener("pointermove", this.onSelectMove);
         document.removeEventListener("pointerup", this.onSelectEnd);
     }
 
@@ -468,7 +477,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     highlight = (targetDoc: Doc | undefined, color: string) => {
         // creates annotation documents for current highlights
         let annotationDoc = this.makeAnnotationDocument(targetDoc, color, false);
-        Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc);
+        Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
         return annotationDoc;
     }
 
@@ -529,7 +538,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     removeDocument(doc: Doc): boolean {
         //TODO This won't create the field if it doesn't already exist
         let targetDataDoc = this.props.fieldExtensionDoc;
-        let targetField = "annotations";
+        let targetField = this.props.fieldExt;
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
@@ -539,8 +548,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._mainCont.current.scrollTop) : this.props.ScreenToLocalTransform();
     }
+    setPreviewCursor = (func?: (x: number, y: number) => void) => {
+        this._setPreviewCursor = func;
+    }
+    onClick = (e: React.MouseEvent) => {
+        this._setPreviewCursor && this._marqueeing && Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 &&
+            this._setPreviewCursor(e.clientX, e.clientY);
+    }
+    whenActiveChanged = (isActive: boolean) => {
+        this._isChildActive = isActive;
+        //this.props.whenActiveChanged(isActive); // bcz: is this needed here?
+    }
+    active = () => {
+        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+    }
     render() {
-        return (<div className="pdfViewer-viewer" onPointerDown={this.onPointerDown} onWheel={(e) => e.stopPropagation()} ref={this._mainCont}>
+        return (<div className="pdfViewer-viewer" onPointerDown={this.onPointerDown} onWheel={(e) => e.stopPropagation()} onClick={this.onClick} ref={this._mainCont}>
             <div className="pdfViewer-text" ref={this._viewer} />
             <div className="pdfViewer-dragAnnotationBox" ref={this._marquee}
                 style={{
@@ -554,17 +577,24 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
             </div>
             <CollectionFreeFormView {...this.props}
-                fieldExt="annotations"
+                setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={() => this._pageSizes.length && this._pageSizes[0] ? this.props.pdf.numPages * this._pageSizes[0].height : 300}
+                PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : 300}
+                focus={emptyFunction}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
-                addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", doc); return true; }}
+                addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
                 CollectionView={this.props.ContainingCollectionView}
                 ScreenToLocalTransform={this.scrollXf}
-                ruleProvider={this.props.ruleProvider}
-                chromeCollapsed={true}
-                layoutKey={undefined}
-                backgroundLayout={undefined} >
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document}
+                chromeCollapsed={true}>
             </CollectionFreeFormView>
         </div >);
     }
-- 
cgit v1.2.3-70-g09d2


From 288a3a1368ab7de11007080e54cd1879196342a4 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 26 Sep 2019 14:37:49 -0400
Subject: hopefully last fixes for pdf interactions -- this time for marquee
 dragging with right button.

---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 36 +++++++++++++---------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 -
 src/client/views/nodes/DocumentView.tsx            | 10 +++---
 src/client/views/pdf/PDFViewer.tsx                 | 15 +++++----
 5 files changed, 36 insertions(+), 28 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ce80526b2..9ffb7fa6d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -30,7 +30,7 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     chromeCollapsed: boolean;
-    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
+    setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 689a55ec4..44611869e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,7 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay: boolean;
-    setPreviewCursor?: (func: (x: number, y: number) => void) => void;
+    setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
 @observer
@@ -151,15 +151,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = this._lastX = e.pageX;
-        this._downY = this._lastY = e.pageY;
-        this._commandExecuted = false;
-        PreviewCursor.Visible = false;
-        this.cleanupInteractions(true);
+        this._downX = this._lastX = e.clientX;
+        this._downY = this._lastY = e.clientY;
         if (e.button === 2 || (e.button === 0 && e.altKey)) {
-            document.addEventListener("pointermove", this.onPointerMove, true);
-            document.addEventListener("pointerup", this.onPointerUp, true);
-            document.addEventListener("keydown", this.marqueeCommand, true);
+            this.setPreviewCursor(e.clientX, e.clientY, true);
             if (e.altKey) {
                 //e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
                 e.preventDefault();
@@ -182,6 +177,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 e.stopPropagation();
                 e.preventDefault();
             }
+        } else {
+            this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
         }
         if (e.altKey) {
             e.preventDefault();
@@ -208,17 +205,28 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
     }
 
-    setPreviewCursor = (x: number, y: number) => {
-        this._downX = x;
-        this._downY = y;
-        PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+    setPreviewCursor = (x: number, y: number, drag: boolean) => {
+        if (drag) {
+            this._downX = this._lastX = x;
+            this._downY = this._lastY = y;
+            this._commandExecuted = false;
+            PreviewCursor.Visible = false;
+            this.cleanupInteractions(true);
+            document.addEventListener("pointermove", this.onPointerMove, true);
+            document.addEventListener("pointerup", this.onPointerUp, true);
+            document.addEventListener("keydown", this.marqueeCommand, true);
+        } else {
+            this._downX = x;
+            this._downY = y;
+            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+        }
     }
 
     @action
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            this.setPreviewCursor(e.clientX, e.clientY);
+            this.setPreviewCursor(e.clientX, e.clientY, false);
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {  // why do we get a click event when the cursor have moved a big distance?
             // let's cut it off here so no one else has to deal with it.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index cd183a984..49b6f22db 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -103,7 +103,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
 
     render() {
-        trace();
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d1d150027..0d11afbe1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -254,21 +254,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this._hitTemplateDrag = true;
             }
         }
-        if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
+        if (this.active && e.button === 0) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.addEventListener("pointermove", this.onPointerMove);
         document.addEventListener("pointerup", this.onPointerUp);
     }
     onPointerMove = (e: PointerEvent): void => {
-        console.log("Move " + e.clientX + " " + this.props.Document.title);
         if (e.cancelBubble && this.active) {
-            document.removeEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) ||
-            this.props.parentActive())) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive())) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
-                if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) {
+                if (!e.altKey && !this.topMost && e.buttons === 1 && !this.Document.lockedPosition) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
                     this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 427da1d9b..9ef311c86 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -69,7 +69,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     public pdfViewer: any;
     private _isChildActive = false;
-    private _setPreviewCursor: undefined | ((x: number, y: number) => void);
+    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
     private _annotationReactionDisposer?: IReactionDisposer;
@@ -355,7 +355,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         // if alt+left click, drag and annotate
+        this._downX = e.clientX;
+        this._downY = e.clientY;
         if (NumCast(this.props.Document.scale, 1) !== 1) return;
+        if (e.button !== 0 && this.active()) {
+            this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
+        }
         this._marqueeing = false;
         if (!e.altKey && e.button === 0 && this.active()) {
             PDFMenu.Instance.StartDrag = this.startDrag;
@@ -370,8 +375,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }
             }
             else {
-                this._downX = e.clientX;
-                this._downY = e.clientY;
                 // set marquee x and y positions to the spatially transformed position
                 if (this._mainCont.current) {
                     let boundingRect = this._mainCont.current.getBoundingClientRect();
@@ -564,15 +567,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._mainCont.current.scrollTop) : this.props.ScreenToLocalTransform();
     }
-    setPreviewCursor = (func?: (x: number, y: number) => void) => {
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
         this._setPreviewCursor = func;
     }
     onClick = (e: React.MouseEvent) => {
         this._setPreviewCursor &&
-            this._marqueeing &&
+            e.button === 0 &&
             Math.abs(e.clientX - this._downX) < 3 &&
             Math.abs(e.clientY - this._downY) < 3 &&
-            this._setPreviewCursor(e.clientX, e.clientY);
+            this._setPreviewCursor(e.clientX, e.clientY, false);
     }
     whenActiveChanged = (isActive: boolean) => {
         this._isChildActive = isActive;
-- 
cgit v1.2.3-70-g09d2


From a7fc637fb8a9ecf52e737a1d0e28b3805193b82e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 28 Sep 2019 23:43:25 -0400
Subject: lots of fixes to pdfs and link following.

---
 src/Utils.ts                                       | 30 +++++++++
 src/client/util/DocumentManager.ts                 |  4 +-
 src/client/views/DocumentDecorations.tsx           |  8 +--
 src/client/views/MainView.tsx                      |  5 +-
 .../views/collections/CollectionDockingView.scss   |  5 +-
 .../views/collections/CollectionDockingView.tsx    | 63 ++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 51 ++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  3 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  6 +-
 src/client/views/nodes/DocumentView.tsx            |  9 ++-
 src/client/views/nodes/FormattedTextBox.tsx        | 75 +++++++++++-----------
 src/client/views/nodes/ImageBox.tsx                |  1 +
 src/client/views/nodes/PDFBox.tsx                  | 20 ++++--
 src/client/views/pdf/PDFViewer.tsx                 | 45 +++++++------
 src/new_fields/Doc.ts                              |  2 +-
 15 files changed, 184 insertions(+), 143 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 4fac53c7d..2b00a6530 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -307,4 +307,34 @@ export function PostToServer(relativeRoute: string, body: any) {
         body: body
     };
     return requestPromise.post(options);
+}
+
+const easeInOutQuad = (currentTime: number, start: number, change: number, duration: number) => {
+    let newCurrentTime = currentTime / (duration / 2);
+
+    if (newCurrentTime < 1) {
+        return (change / 2) * newCurrentTime * newCurrentTime + start;
+    }
+
+    newCurrentTime -= 1;
+    return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
+};
+
+export default function smoothScroll(duration: number, element: HTMLElement, to: number) {
+    const start = element.scrollTop;
+    const change = to - start;
+    const startDate = new Date().getTime();
+
+    const animateScroll = () => {
+        const currentDate = new Date().getTime();
+        const currentTime = currentDate - startDate;
+        element.scrollTop = easeInOutQuad(currentTime, start, change, duration);
+
+        if (currentTime < duration) {
+            requestAnimationFrame(animateScroll);
+        } else {
+            element.scrollTop = to;
+        }
+    };
+    animateScroll();
 }
\ No newline at end of file
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e60ab09bb..c048125c5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -132,7 +132,7 @@ export class DocumentManager {
         let doc = Doc.GetProto(docDelegate);
         const contextDoc = await Cast(doc.annotationOn, Doc);
         if (contextDoc) {
-            contextDoc.panY = doc.y;
+            contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2;
         }
 
         let docView: DocumentView | null;
@@ -178,7 +178,7 @@ export class DocumentManager {
                     (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined);
                     setTimeout(() => {
                         this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
-                    }, 10);
+                    }, 1000);
                 }
             }
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index cacaddcc8..944ae586c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -515,8 +515,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
                 let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
-                let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight);
-                if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) {
+                let fixedAspect = e.ctrlKey || (!doc.ignoreAspect && nwidth && nheight);
+                if (fixedAspect && e.ctrlKey && doc.ignoreAspect) {
                     doc.ignoreAspect = false;
                     proto.nativeWidth = nwidth = doc.width || 0;
                     proto.nativeHeight = nheight = doc.height || 0;
@@ -531,7 +531,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                             Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true);
                         }
                         doc.width = actualdW;
-                        if (fixedAspect) doc.height = nheight / nwidth * doc.width;
+                        if (fixedAspect && !doc.fitWidth) doc.height = nheight / nwidth * doc.width;
                         else doc.height = actualdH;
                     }
                     else {
@@ -539,7 +539,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                             Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true);
                         }
                         doc.height = actualdH;
-                        if (fixedAspect) doc.width = nwidth / nheight * doc.height;
+                        if (fixedAspect && !doc.fitWidth) doc.width = nwidth / nheight * doc.height;
                         else doc.width = actualdW;
                     }
                 } else {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 244b217ed..0d1546e68 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -505,6 +505,7 @@ export class MainView extends React.Component {
                     <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button></li>
                     <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button></li>
                     <li key="inkControls"><InkingControl /></li>
+                    <li key="logout"><button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></li>
                 </ul>
             </div>
         </div >;
@@ -520,12 +521,8 @@ export class MainView extends React.Component {
     /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
     @computed
     get miscButtons() {
-        let logoutRef = React.createRef<HTMLDivElement>();
-
         return [
             this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
-            <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></div>
         ];
 
     }
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 0e7e0afa7..6f5abd05b 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,8 +1,5 @@
 @import "../../views/globalCssVariables.scss";
 
-.collectiondockingview-content {
-    height: 100%;
-}
 .lm_active .messageCounter{
     color:white;
     background: #999999;
@@ -21,7 +18,7 @@
 
 .collectiondockingview-container {
     width: 100%;
-    height: 100%;
+    height:100%;
     border-style: solid;
     border-width: $COLLECTION_BORDER_WIDTH;
     position: absolute;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b6bc4b4ba..b047e77a8 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -32,6 +32,7 @@ import React = require("react");
 import { ButtonSelector } from './ParentDocumentSelector';
 import { DocumentType } from '../../documents/DocumentTypes';
 library.add(faFile);
+const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -534,12 +535,11 @@ interface DockedFrameProps {
 }
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
-    _mainCont: HTMLDivElement | undefined = undefined;
+    _mainCont: HTMLDivElement | null = null;
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
     @observable private _document: Opt<Doc>;
     @observable private _dataDoc: Opt<Doc>;
-
     @observable private _isActive: boolean = false;
 
     get _stack(): any {
@@ -577,6 +577,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     componentDidMount() {
+        let observer = new _global.ResizeObserver(action((entries: any) => {
+            for (let entry of entries) {
+                this._panelWidth = entry.contentRect.width;
+                this._panelHeight = entry.contentRect.height;
+            }
+        }));
+        observer.observe(this.props.glContainer._element[0]);
         this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
         this.props.glContainer.on("tab", this.onActiveContentItemChanged);
         this.onActiveContentItemChanged();
@@ -595,15 +602,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
 
-    panelWidth = () => this._document!.ignoreAspect ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth()));
-    panelHeight = () => this._document!.ignoreAspect ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight()));
+    panelWidth = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth()));
+    panelHeight = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight()));
 
-    nativeWidth = () => !this._document!.ignoreAspect ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0;
-    nativeHeight = () => !this._document!.ignoreAspect ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0;
+    nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0;
+    nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0;
 
     contentScaling = () => {
         if (this._document!.type === DocumentType.PDF) {
-            if (this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) {
+            if ((this._document && this._document.fitWidth) ||
+                this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) {
                 return this._panelWidth / NumCast(this._document!.nativeWidth);
             } else {
                 return this._panelHeight / NumCast(this._document!.nativeHeight);
@@ -639,13 +647,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
         }
     }
-    @computed get docView() {
-        if (!this._document) {
-            return (null);
-        }
-        let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
-        return <DocumentView key={this._document[Id]}
-            Document={this._document}
+    docView(document: Doc) {
+        let resolvedDataDoc = document.layout instanceof Doc ? document : this._dataDoc;
+        return <DocumentView key={document[Id]}
+            Document={document}
             DataDoc={resolvedDataDoc}
             bringToFront={emptyFunction}
             addDocument={undefined}
@@ -668,28 +673,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             getScale={returnOne} />;
     }
 
-    @computed get content() {
-        return (
-            <div className="collectionDockingView-content" ref={action((ref: HTMLDivElement) => {
-                this._mainCont = ref;
-                if (ref) {
-                    this._panelWidth = Number(getComputedStyle(ref).width!.replace("px", ""));
-                    this._panelHeight = Number(getComputedStyle(ref).height!.replace("px", ""));
-                }
-            })}
-                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
-                {this.docView}
-            </div >);
-    }
-
     render() {
-        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; })}>
-                {({ measureRef }) => <div ref={measureRef}>
-                    {theContent}
-                </div>}
-            </Measure>;
+        return (!this._isActive || !this._document) ? (null) :
+            (<div className="collectionDockingView-content" ref={ref => this._mainCont = ref}
+                style={{
+                    transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`,
+                    height: this._document && this._document.fitWidth ? undefined : "100%"
+                }}>
+                {this.docView(this._document)}
+            </div >);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 721732774..4b260d111 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -401,27 +401,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
         }
         SelectionManager.DeselectAll();
-        const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
-        const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
-        const newState = HistoryUtil.getState();
-        newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
-        HistoryUtil.pushState(newState);
-
-        let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
-
-        this.setPan(newPanX, newPanY);
-        this.Document.panTransformType = "Ease";
-        this.props.focus(this.props.Document);
-        willZoom && this.setScaleToZoom(doc, scale);
-
-        afterFocus && setTimeout(() => {
-            if (afterFocus && afterFocus()) {
-                this.Document.panX = savedState.px;
-                this.Document.panY = savedState.py;
-                this.Document.scale = savedState.s;
-                this.Document.panTransformType = savedState.pt;
-            }
-        }, 1000);
+        if (this.props.Document.scrollHeight) {
+            let annotOn = Cast(doc.annotationOn, Doc) as Doc;
+            let offset = annotOn && (NumCast(annotOn.height) / 2);
+            this.props.Document.scrollY = NumCast(doc.y) - offset;
+        } else {
+            const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+            const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+            const newState = HistoryUtil.getState();
+            newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
+            HistoryUtil.pushState(newState);
+
+            let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
+
+            this.setPan(newPanX, newPanY);
+            this.Document.panTransformType = "Ease";
+            this.props.focus(this.props.Document);
+            willZoom && this.setScaleToZoom(doc, scale);
+
+            afterFocus && setTimeout(() => {
+                if (afterFocus && afterFocus()) {
+                    this.Document.panX = savedState.px;
+                    this.Document.panY = savedState.py;
+                    this.Document.scale = savedState.s;
+                    this.Document.panTransformType = savedState.pt;
+                }
+            }, 1000);
+        }
+
     }
 
     setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
@@ -449,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
-            ContainingCollectionDoc: this.props.ContainingCollectionDoc,
+            ContainingCollectionDoc: this.props.Document,
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
             parentActive: this.props.active,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 44611869e..82193aefa 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -188,16 +188,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @action
     onPointerUp = (e: PointerEvent): void => {
         if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
-        // console.log("pointer up!");
         if (this._visible) {
-            // console.log("visible");
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
             }
             this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
         }
-        //console.log("invisible");
         this.cleanupInteractions(true);
 
         if (e.altKey) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c3d2c9e51..c4fed200f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,7 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     borderRounding = () => {
         let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
-        let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout as Doc : undefined;
+        let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout : undefined;
         let br = StrCast((ld || this.props.Document).borderRounding);
         br = !br && ruleRounding ? ruleRounding : br;
         if (br.endsWith("%")) {
@@ -100,7 +100,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @observable _animPos: number[] | undefined = undefined;
 
-    finalPanelWidh = () => this.dataProvider ? this.dataProvider.width : this.panelWidth();
+    finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth();
     finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
 
     render() {
@@ -124,7 +124,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
                     backgroundColor={this.clusterColorFunc}
-                    PanelWidth={this.finalPanelWidh}
+                    PanelWidth={this.finalPanelWidth}
                     PanelHeight={this.finalPanelHeight}
                 />
             </div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6ee88f834..bd702c6a2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -223,15 +223,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         else if (linkedDocs.length) {
             SelectionManager.DeselectAll();
             let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored);
+            let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored);
             let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
+            let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
             if (firstUnshown.length) first = [firstUnshown[0]];
-            let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]];
+            if (secondUnshown.length) second = [secondUnshown[0]];
+            let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined;
 
             // @TODO: shouldn't always follow target context
             let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined];
             let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined];
 
-            if (!linkedFwdDocs.some(l => l instanceof Promise)) {
+            if (linkedFwdDocs && !linkedFwdDocs.some(l => l instanceof Promise)) {
                 let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
                 let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
                 DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
@@ -601,7 +604,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
 
         const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%";
-        const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%";
+        const nativeHeight = this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%";
         const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
         const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
         const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 923dd1544..565fb0505 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -815,6 +815,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
         }
+        let ctrlKey = e.ctrlKey;
+        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+            e.preventDefault();
+        }
+    }
+
+    onPointerUp = (e: React.PointerEvent): void => {
+        FormattedTextBoxComment.textBox = this;
+        if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+            e.stopPropagation();
+        }
+    }
+
+    @action
+    onFocused = (e: React.FocusEvent): void => {
+        document.removeEventListener("keypress", this.recordKeyHandler);
+        document.addEventListener("keypress", this.recordKeyHandler);
+        this.tryUpdateHeight();
+        if (!this.props.isOverlay) {
+            FormattedTextBox.InputBoxOverlay = this;
+        } else {
+            if (this._ref.current) {
+                this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
+            }
+        }
+    }
+    onPointerWheel = (e: React.WheelEvent): void => {
+        // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
+        if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+            e.stopPropagation();
+        }
+    }
+
+    onClick = (e: React.MouseEvent): void => {
         let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
@@ -829,8 +863,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
             if (node) {
                 let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
-                href = link && link.attrs.href;
-                location = link && link.attrs.location;
+                if (link && !(link.attrs.docref && link.attrs.title)) {  // bcz: getting hacky.  this indicates that we clicked on a PDF excerpt quotation.  In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
+                    href = link && link.attrs.href;
+                    location = link && link.attrs.location;
+                }
             }
             if (href) {
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
@@ -848,7 +884,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                                         return;
                                     }
                                 }
-                                if (targetContext) {
+                                if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) {
                                     DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
                                 } else if (jumpToDoc) {
                                     DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
@@ -870,39 +906,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
 
         }
-        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
-            e.preventDefault();
-        }
-    }
-
-    onPointerUp = (e: React.PointerEvent): void => {
-        FormattedTextBoxComment.textBox = this;
-        if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
-            e.stopPropagation();
-        }
-    }
-
-    @action
-    onFocused = (e: React.FocusEvent): void => {
-        document.removeEventListener("keypress", this.recordKeyHandler);
-        document.addEventListener("keypress", this.recordKeyHandler);
-        this.tryUpdateHeight();
-        if (!this.props.isOverlay) {
-            FormattedTextBox.InputBoxOverlay = this;
-        } else {
-            if (this._ref.current) {
-                this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
-            }
-        }
-    }
-    onPointerWheel = (e: React.WheelEvent): void => {
-        // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
-        if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
-            e.stopPropagation();
-        }
-    }
-
-    onClick = (e: React.MouseEvent): void => {
         // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
         if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
             let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 624593245..004f50590 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -38,6 +38,7 @@ library.add(faFileAudio, faAsterisk);
 
 export const pageSchema = createSchema({
     curPage: "number",
+    fitWidth: "boolean"
 });
 
 interface Window {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 0fcbaaa7c..fe71e76fd 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -20,6 +20,9 @@ import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
 import { undoBatch } from '../../util/UndoManager';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ContextMenu } from '../ContextMenu';
+import { Utils } from '../../../Utils';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -58,7 +61,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             this.Document.nativeWidth = nw * 96 / 72;
             this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72;
         }
-        this.Document.height = this.Document[WidthSym]() * (nh / nw);
+        !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
     }
 
     public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
@@ -165,14 +168,23 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             </div>);
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
+        let funcs: ContextMenuProps[] = [];
+        pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" });
+
+        ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
+    }
+
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive");
         return (!(pdfUrl instanceof PdfField) || !this._pdf ?
             <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
-            <div className={classname} onPointerDown={(e: React.PointerEvent) => {
+            <div className={classname} onContextMenu={this.specificContextMenu} onPointerDown={(e: React.PointerEvent) => {
                 let hit = document.elementFromPoint(e.clientX, e.clientY);
-                if (hit && hit.localName === "span" && this.props.isSelected()) {
+                if (hit && hit.localName === "span" && this.props.isSelected()) {  // drag selecting text stops propagation
                     e.button === 0 && e.stopPropagation();
                 }
             }}>
@@ -182,7 +194,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
                     addDocTab={this.props.addDocTab} GoToPage={this.gotoPage}
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
-                    ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                    ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
                     isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
                     fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} />
                 {this.settingsPanel()}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5ad4ffd48..13fd8ea98 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,14 +9,12 @@ import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import Annotation from "./Annotation";
 import PDFMenu from "./PDFMenu";
 import "./PDFViewer.scss";
 import React = require("react");
@@ -24,7 +22,8 @@ import * as rp from "request-promise";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
+import Annotation from "./Annotation";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 
@@ -41,6 +40,7 @@ interface IViewerProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     ContentScaling: () => number;
+    select: (isCtrlPressed: boolean) => void;
     renderDepth: number;
     isSelected: () => boolean;
     loaded: (nw: number, nh: number, np: number) => void;
@@ -106,11 +106,20 @@ export class PDFViewer extends React.Component<IViewerProps> {
         // file address of the pdf
         this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`)));
         runInAction(() => this._showWaiting = this._showCover = true);
-        this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => {
-            this.setupPdfJsViewer();
-            this._selectionReactionDisposer && this._selectionReactionDisposer();
-            this._selectionReactionDisposer = undefined;
-        })
+        this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.setupPdfJsViewer());
+        this._reactionDisposer = reaction(
+            () => this.props.Document.scrollY,
+            (scrollY) => {
+                if (scrollY !== undefined) {
+                    if (this._showCover || this._showWaiting) {
+                        this.setupPdfJsViewer();
+                    }
+                    this._mainCont.current && smoothScroll(1000, this._mainCont.current, NumCast(this.props.Document.scrollY) || 0);
+                    this.props.Document.scrollY = undefined;
+                }
+            },
+            { fireImmediately: true }
+        );
     }
 
     componentWillUnmount = () => {
@@ -153,12 +162,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]),
                         (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i);
                 }))));
-            Doc.GetProto(this.props.Document).scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0);
+            Doc.GetProto(this.props.Document).scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
         }
     }
 
     @action
     setupPdfJsViewer = async () => {
+        this._selectionReactionDisposer && this._selectionReactionDisposer();
+        this._selectionReactionDisposer = undefined;
         this._showWaiting = true;
         this.props.setPdfViewer(this);
         await this.initialLoad();
@@ -180,10 +191,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             }),
             { fireImmediately: true }
         );
-        this._reactionDisposer = reaction(
-            () => this.props.Document.panY,
-            () => this._mainCont.current && this._mainCont.current.scrollTo({ top: NumCast(this.props.Document.panY) || 0, behavior: "auto" })
-        );
 
         document.removeEventListener("copy", this.copy);
         document.addEventListener("copy", this.copy);
@@ -228,10 +235,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
             annoDocs.push(annoDoc);
             annoDoc.isButton = true;
             anno.remove();
-            // this.props.addDocument && this.props.addDocument(annoDoc, false);
             mainAnnoDoc = annoDoc;
+            mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
             mainAnnoDocProto.y = annoDoc.y;
-            mainAnnoDocProto = Doc.GetProto(annoDoc);
         } else {
             this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => {
                 let annoDoc = new Doc();
@@ -381,7 +387,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this._downX = e.clientX;
         this._downY = e.clientY;
         if (NumCast(this.props.Document.scale, 1) !== 1) return;
-        if (e.button !== 0 && this.active()) {
+        if ((e.button !== 0 || e.altKey) && this.active()) {
             this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
         }
         this._marqueeing = false;
@@ -638,18 +644,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     @computed get annotationLayer() {
-        trace();
         return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
                 <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
     }
     @computed get pdfViewerDiv() {
-        trace();
         return <div className="pdfViewer-text" ref={this._viewer} style={{ transformOrigin: "left top" }} />;
     }
     @computed get standinViews() {
-        trace();
         return <>
             {this._showCover ? this.getCoverImage() : (null)}
             {this._showWaiting ? <img className="pdfViewer-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
@@ -710,7 +713,7 @@ class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
                 width: `${this.props.width()}px`, height: `${this.props.height()}px`,
                 border: `${this.props.width() === 0 ? "" : "2px dashed black"}`
             }}>
-        </div>
+        </div>;
     }
 }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1b3c8b0b0..79b73aba8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -344,7 +344,7 @@ export namespace Doc {
         let list = Cast(target[key], listSpec(Doc));
         if (list) {
             if (allowDuplicates !== true) {
-                let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1);
+                let pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1);
                 if (pind !== -1) {
                     list.splice(pind, 1);
                 }
-- 
cgit v1.2.3-70-g09d2


From 53685a27139886c1df74840cc9f451c046a32de6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 4 Oct 2019 01:50:57 -0400
Subject: got rid of MainOverlayTextBox!  fixed some things with embedding docs
 in text

---
 src/client/util/RichTextSchema.tsx                 |   4 +-
 src/client/views/InkingControl.tsx                 |   3 +-
 src/client/views/MainOverlayTextBox.scss           |  29 ----
 src/client/views/MainOverlayTextBox.tsx            | 156 ---------------------
 src/client/views/MainView.tsx                      |   5 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |   6 -
 .../collectionFreeForm/MarqueeView.scss            |   7 +
 .../collections/collectionFreeForm/MarqueeView.tsx |   4 +-
 src/client/views/nodes/FormattedTextBox.scss       |   7 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  52 ++-----
 10 files changed, 31 insertions(+), 242 deletions(-)
 delete mode 100644 src/client/views/MainOverlayTextBox.scss
 delete mode 100644 src/client/views/MainOverlayTextBox.tsx

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 7be773475..12cf40b32 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -720,8 +720,8 @@ export class DashDocView {
         this._outer.style.overflow = "hidden";
         (this._outer.style as any).float = node.attrs.float;
 
-        this._dashSpan.style.width = "100%";
-        this._dashSpan.style.height = "100%";
+        this._dashSpan.style.width = node.attrs.width;
+        this._dashSpan.style.height = node.attrs.height;
         this._dashSpan.style.position = "absolute";
         this._dashSpan.style.display = "inline-block"
         this._handle.style.position = "absolute";
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index a10df0e75..ee8b77050 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -10,7 +10,6 @@ import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { StrCast, NumCast, Cast } from "../../new_fields/Types";
-import { MainOverlayTextBox } from "./MainOverlayTextBox";
 import { listSpec } from "../../new_fields/Schema";
 import { List } from "../../new_fields/List";
 import { Utils } from "../../Utils";
@@ -46,7 +45,7 @@ export class InkingControl extends React.Component {
     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;
+            // if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
             let selected = SelectionManager.SelectedDocuments();
             let oldColors = selected.map(view => {
                 let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
deleted file mode 100644
index c9d44e194..000000000
--- a/src/client/views/MainOverlayTextBox.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-@import "globalCssVariables";
-
-.mainOverlayTextBox-textInput {
-    background-color: rgba(248, 6, 6, 0.001);
-    width: 400px;
-    height: 200px;
-    position: absolute;
-    overflow: visible;
-    top: 0;
-    left: 0;
-    pointer-events: none;
-    z-index: $mainTextInput-zindex;
-
-    .formattedTextBox-cont {
-        background-color: rgba(248, 6, 6, 0.001);
-        width: 100%;
-        height: 100%;
-        position: absolute;
-        top: 0;
-        left: 0;
-    }
-}
-
-.mainOverlayTextBox-unscaled_div {
-    // width: 0px;
-    z-index: 10000;
-    position: absolute;
-    pointer-events: none;
-}
\ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
deleted file mode 100644
index 73eef9478..000000000
--- a/src/client/views/MainOverlayTextBox.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import { action, observable, reaction, trace } from 'mobx';
-import { observer } from 'mobx-react';
-import "normalize.css";
-import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
-import { BoolCast } from '../../new_fields/Types';
-import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils';
-import { DragManager } from '../util/DragManager';
-import { Transform } from '../util/Transform';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import "./MainOverlayTextBox.scss";
-import { FormattedTextBox } from './nodes/FormattedTextBox';
-
-interface MainOverlayTextBoxProps {
-    firstinstance?: boolean;
-}
-
-@observer
-export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
-    public static Instance: MainOverlayTextBox;
-    @observable _textXf: () => Transform = () => Transform.Identity();
-    public TextFieldKey: string = "data";
-    private _textColor: string | null = null;
-    private _textHideOnLeave?: boolean;
-    private _textTargetDiv: HTMLDivElement | undefined;
-    private _textProxyDiv: React.RefObject<HTMLDivElement>;
-    private _textBottom: boolean | undefined;
-    private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); };
-    private _outerdiv: HTMLElement | null = null;
-    private _textBox: FormattedTextBox | undefined;
-    private _tooltip?: HTMLElement;
-    ChromeHeight?: () => number;
-    @observable public TextDoc?: Doc;
-    @observable public TextDataDoc?: Doc;
-
-    updateTooltip = () => {
-        this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip);
-    }
-
-    public SetColor(color: string) {
-        return this._textBox && this._textBox.setFontColor(color);
-    }
-
-    constructor(props: MainOverlayTextBoxProps) {
-        super(props);
-        this._textProxyDiv = React.createRef();
-        MainOverlayTextBox.Instance = this;
-        reaction(() => FormattedTextBox.InputBoxOverlay,
-            (box?: FormattedTextBox) => {
-                this._textBox = box;
-                if (box) {
-                    this.ChromeHeight = box.props.ChromeHeight;
-                    this.TextDoc = box.props.Document;
-                    this.TextDataDoc = box.props.DataDoc;
-                    let xf = () => {
-                        box.props.ScreenToLocalTransform();
-                        let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
-                        return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale);
-                    };
-                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight) || box.props.height === "min-content");
-                }
-                else {
-                    this.TextDoc = undefined;
-                    this.TextDataDoc = undefined;
-                    this.setTextDoc();
-                }
-            });
-    }
-
-    @action
-    private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform, autoHeight?: boolean) {
-        if (this._textTargetDiv) {
-            this._textTargetDiv.style.color = this._textColor;
-        }
-        this.TextFieldKey = textFieldKey!;
-        let txf = tx ? tx : () => Transform.Identity();
-        this._textXf = txf;
-        this._textTargetDiv = div;
-        this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave;
-        if (div) {
-            let parTop = div.parentElement && getComputedStyle(div.parentElement).top;
-            this._textBottom = parTop && parTop !== "0px" && parTop != "auto" ? true : false;
-            this._textColor = (getComputedStyle(div) as any).color;
-            div.style.color = "transparent";
-        }
-    }
-
-    @action
-    textScroll = (e: React.UIEvent) => {
-        if (this._textProxyDiv.current && this._textTargetDiv) {
-            this._textTargetDiv.scrollTop = (e as any)._targetInst.stateNode.scrollTop;
-        }
-    }
-
-    textBoxDown = (e: React.PointerEvent) => {
-        if (e.button !== 0 || e.metaKey || e.altKey) {
-            document.addEventListener("pointermove", this.textBoxMove);
-            document.addEventListener('pointerup', this.textBoxUp);
-        }
-    }
-    @action
-    textBoxMove = (e: PointerEvent) => {
-        if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) {
-            document.removeEventListener("pointermove", this.textBoxMove);
-            document.removeEventListener('pointerup', this.textBoxUp);
-            let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]);
-            const [left, top] = this._textXf().inverse().transformPoint(0, 0);
-            dragData.offset = [e.clientX - left, e.clientY - top];
-            DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
-                handlers: {
-                    dragComplete: action(emptyFunction),
-                },
-                hideSource: false
-            });
-        }
-    }
-    textBoxUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.textBoxMove);
-        document.removeEventListener('pointerup', this.textBoxUp);
-    }
-
-    addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {
-        return this._textBox && this._textBox.props.addDocTab(doc, dataDoc, location) ? true : false;
-    }
-    render() {
-        this.TextDoc; this.TextDataDoc;
-        if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
-            let wid = FormattedTextBox.InputBoxOverlay.props.Document.width; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
-            let hgtx = FormattedTextBox.InputBoxOverlay.props.Document.height; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
-            let textRect = this._textTargetDiv.getBoundingClientRect();
-            let s = this._textXf().Scale;
-            let location = this._textBottom ? textRect.bottom : textRect.top;
-            let hgt = (this._textBox && this._textBox.props.Document.autoHeight) || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
-            return <div ref={this._setouterdiv} className="mainOverlayTextBox-unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} >
-                <div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s})`, width: "auto", height: "0px" }} >
-                    <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
-                        style={{ width: `${textRect.width * s}px`, height: "0px" }}>
-                        <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
-                            <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} fieldExt="" hideOnLeave={this._textHideOnLeave} isOverlay={true}
-                                Document={FormattedTextBox.InputBoxOverlay.props.Document}
-                                DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
-                                onClick={undefined}
-                                ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined}
-                                ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0}
-                                ContainingCollectionDoc={undefined} ContainingCollectionView={undefined}
-                                whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
-                                ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
-                                pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
-                        </div>
-                    </div>
-                </div>
-            </ div>;
-        }
-        else return (null);
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 660b42290..545f99a41 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -34,14 +34,12 @@ import { DocumentDecorations } from './DocumentDecorations';
 import KeyManager from './GlobalKeyHandler';
 import { InkingControl } from './InkingControl';
 import "./Main.scss";
-import { MainOverlayTextBox } from './MainOverlayTextBox';
 import MainViewModal from './MainViewModal';
 import { DocumentView } from './nodes/DocumentView';
-import { PresBox } from './nodes/PresBox';
-import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { FilterBox } from './search/FilterBox';
+import { OverlayView } from './OverlayView';
 
 @observer
 export class MainView extends React.Component {
@@ -687,7 +685,6 @@ export class MainView extends React.Component {
                 {this.nodesMenu()}
                 {this.miscButtons}
                 <PDFMenu />
-                <MainOverlayTextBox firstinstance={true} />
                 <OverlayView />
             </div >
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index c4311fa52..bb1a12f88 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -46,12 +46,6 @@
     border-radius: inherit;
     box-sizing: border-box;
     position: absolute;
-    overflow: hidden;
-
-    .marqueeView {
-        overflow: hidden;
-    }
-
     top: 0;
     left: 0;
     width: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 9fc2e44fb..7dc54ea79 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -6,6 +6,13 @@
     width:100%;
     height:100%;
 }
+.marqueeView {
+    overflow: hidden;
+}
+
+.marqueeView:focus-within {
+    overflow: visible;
+}
 .marquee {
     border-style: dashed;
     box-sizing: border-box;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 82193aefa..bf154d37d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -442,8 +442,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     render() {
         let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
-        return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
-            <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
+        return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+            <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} onScroll={(e) => e.currentTarget.scrollLeft = 0}  >
                 {this._visible ? this.marqueeDiv : null}
                 <div ref={this._mainCont} style={{ transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
                     {this.props.children}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 45e516015..541c29faa 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -28,7 +28,7 @@
 }
 
 .formattedTextBox-cont-hidden {
-    pointer-events: none;
+   // pointer-events: none;
 }
 
 .formattedTextBox-inner-rounded {
@@ -40,9 +40,10 @@
     left: 10%;
 }
 
-.formattedTextBox-inner-rounded div,
-.formattedTextBox-inner div {
+.formattedTextBox-inner-rounded ,
+.formattedTextBox-inner  {
     padding: 10px 10px;
+    height: 100%;
 }
 
 .menuicon {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 819accf20..13eb78f48 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -46,7 +46,6 @@ library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
 
 export interface FormattedTextBoxProps {
-    isOverlay?: boolean;
     hideOnLeave?: boolean;
     height?: string;
     color?: string;
@@ -95,9 +94,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     @observable private _fontFamily = "Arial";
     @observable private _fontAlign = "";
     @observable private _entered = false;
-    @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
     public static SelectOnLoad = "";
-    public static InputBoxOverlayScroll: number = 0;
     public static IsFragment(html: string) {
         return html.indexOf("data-pm-slice") !== -1;
     }
@@ -137,9 +134,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     constructor(props: FieldViewProps) {
         super(props);
-        if (this.props.isOverlay) {
-            DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
-        }
         FormattedTextBox.Instance = this;
     }
 
@@ -300,8 +294,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 node = model.create({ src: url, docid: link[Id] })
             } else {
                 node = schema.nodes.dashDoc.create({
-                    width: this.props.Document[WidthSym](), height: this.props.Document[HeightSym](),
-                    title: "dashDoc", docid: target[Id]
+                    width: target[WidthSym](), height: target[HeightSym](),
+                    title: "dashDoc", docid: target[Id],
+                    float: "none"
                 });
             }
             let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
@@ -390,7 +385,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         (schema as any).Document = this.props.Document;
         return {
             schema,
-            plugins: this.props.isOverlay ? [
+            plugins: [
                 inputRules(inpRules),
                 this.tooltipTextMenuPlugin(),
                 history(),
@@ -403,26 +398,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     }
                 }),
                 formattedTextBoxCommentPlugin
-            ] : [
-                    history(),
-                    keymap(this._keymap),
-                    keymap(baseKeymap),
-                ]
+            ]
         };
     }
 
     componentDidMount() {
-
-        if (!this.props.isOverlay) {
-            this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
-                () => {
-                    if (this.props.isSelected()) {
-                        FormattedTextBox.InputBoxOverlay = this;
-                        FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop;
-                    }
-                }, { fireImmediately: true });
-        }
-
         this.pullFromGoogleDoc(this.checkState);
         this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
 
@@ -551,7 +531,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let editor = this._editorView;
                     let ret = findLinkFrag(editor.state.doc.content, editor);
 
-                    if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) {
+                    if (ret.frag.size > 2) {
                         let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
                         if (ret.frag.firstChild) {
                             selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
@@ -754,7 +734,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
-            (this._editorView as any).isOverlay = this.props.isOverlay;
             if (startup) {
                 Doc.GetProto(doc).documentText = undefined;
                 this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
@@ -766,7 +745,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
         }
-        else if (this.props.isOverlay) this._editorView!.focus();
+        this._editorView!.focus();
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
         this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
     }
@@ -824,13 +803,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         document.removeEventListener("keypress", this.recordKeyHandler);
         document.addEventListener("keypress", this.recordKeyHandler);
         this.tryUpdateHeight();
-        if (!this.props.isOverlay) {
-            FormattedTextBox.InputBoxOverlay = this;
-        } else {
-            if (this._ref.current) {
-                this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
-            }
-        }
     }
     onPointerWheel = (e: React.WheelEvent): void => {
         // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -901,7 +873,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 }
             }
         }
-        this._proseRef!.focus();
+        let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+        if (pos && pos.pos > 0) {
+            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new TextSelection(this._editorView!.state.doc.resolve(pos.pos))));
+        }
+        this._editorView!.focus();
         if (this._linkClicked) {
             this._linkClicked = "";
             e.preventDefault();
@@ -959,7 +935,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     tryUpdateHeight() {
         const ChromeHeight = this.props.ChromeHeight;
         let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
-        if (!this.props.isOverlay && !this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) {
+        if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) {
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
             this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
@@ -968,7 +944,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     render() {
-        let style = this.props.isOverlay ? "scroll" : "hidden";
+        let style = "hidden";
         let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
         let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground
             ? "none" : "all";
-- 
cgit v1.2.3-70-g09d2


From f9916faa215297d434aab2357b98d2c4b1fdcb92 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 5 Oct 2019 13:02:43 -0400
Subject: started cleaning up videos.  restored link follwing to timecodes. 
 changed to currentTimecode from curPage

---
 src/client/documents/Documents.ts                  | 11 +++--
 src/client/util/DictationManager.ts                |  1 -
 src/client/util/DocumentManager.ts                 |  7 ++-
 src/client/views/InkingCanvas.tsx                  |  6 +--
 .../views/collections/CollectionBaseView.tsx       |  4 +-
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionVideoView.tsx      |  6 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/linking/LinkFollowBox.tsx         |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  6 +--
 src/client/views/nodes/VideoBox.tsx                | 57 ++++++++++++----------
 src/new_fields/InkField.ts                         |  2 +-
 13 files changed, 58 insertions(+), 50 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 71b9038d4..98f56af01 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -74,6 +74,7 @@ export interface DocumentOptions {
     backgroundLayout?: string;
     chromeStatus?: string;
     curPage?: number;
+    currentTimecode?: number;
     documentText?: string;
     borderRounding?: string;
     schemaColumns?: List<SchemaHeaderField>;
@@ -120,7 +121,7 @@ export namespace Docs {
             }],
             [DocumentType.IMG, {
                 layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
-                options: { curPage: 0 }
+                options: {}
             }],
             [DocumentType.WEB, {
                 layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
@@ -136,7 +137,7 @@ export namespace Docs {
             }],
             [DocumentType.VID, {
                 layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType },
-                options: { curPage: 0 },
+                options: { currentTimecode: 0 },
             }],
             [DocumentType.AUDIO, {
                 layout: { view: AudioBox },
@@ -662,15 +663,17 @@ export namespace DocUtils {
         UndoManager.RunInBatch(() => {
             linkDocProto.type = DocumentType.LINK;
 
-            linkDocProto.targetContext = target.ctx;
-            linkDocProto.sourceContext = source.ctx;
             linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
             linkDocProto.linkDescription = description;
 
             linkDocProto.anchor1 = source.doc;
+            linkDocProto.anchor1Context = source.ctx;
+            linkDocProto.anchor1Timecode = source.doc.currentTimecode;
             linkDocProto.anchor1Groups = new List<Doc>([]);
             linkDocProto.anchor2 = target.doc;
+            linkDocProto.anchor2Context = target.ctx;
             linkDocProto.anchor2Groups = new List<Doc>([]);
+            linkDocProto.anchor2Timecode = target.doc.currentTimecode;
 
             LinkManager.Instance.addLink(linkDocProto);
 
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index cebb56bbe..3b2307073 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -336,7 +336,6 @@ export namespace DictationManager {
                     let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
                     newBox.autoHeight = true;
                     let proto = newBox.proto!;
-                    proto.page = -1;
                     let prompt = "Press alt + r to start dictating here...";
                     let head = 3;
                     let anchor = head + prompt.length;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6fe97edbb..83a69465f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, trace } from 'mobx';
 import { Doc, DocListCastAsync } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { Cast, NumCast, StrCast } from '../../new_fields/Types';
@@ -196,10 +196,13 @@ export class DocumentManager {
         const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs;
         const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined;
         const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined;
-        const linkFollowDocContexts = first.length ? [await first[0].targetContext as Doc, await first[0].sourceContext as Doc] : second.length ? [await second[0].sourceContext as Doc, await second[0].targetContext as Doc] : [undefined, undefined];
+        const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined];
+        const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [undefined, undefined];
         if (linkFollowDocs && linkDoc) {
             const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab");
             const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
+            const target = linkFollowDocs[reverse ? 1 : 0];
+            target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
             DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]);
         }
     }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 1cfa8d644..9ab320eab 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -87,7 +87,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 color: InkingControl.Instance.selectedColor,
                 width: InkingControl.Instance.selectedWidth,
                 tool: InkingControl.Instance.selectedTool,
-                page: NumCast(this.props.Document.curPage, -1)
+                displayTimecode: NumCast(this.props.Document.currentTimecode, -1)
             });
             this.inkData = data;
         }
@@ -150,9 +150,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        let curPage = NumCast(this.props.Document.curPage, -1);
+        let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
-            if (strokeData.page === -1 || (Math.abs(Math.round(strokeData.page) - Math.round(curPage)) < 3)) {
+            if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
                 paths.push(<InkingStroke key={id} id={id}
                     line={strokeData.pathData}
                     count={strokeData.pathData.length}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 38d050e5c..62be1fc31 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -101,8 +101,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
-        var curPage = NumCast(this.props.Document.curPage, -1);
-        Doc.GetProto(doc).page = curPage;
+        var curTime = NumCast(this.props.Document.currentTimecode, -1);
+        curTime !== -1 && (doc.displayTimecode = curTime);
         if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 52a41fc84..1ba35a52b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -51,7 +51,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
     ["title", ColumnType.String],
     ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number],
     ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
-    ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number]
+    ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
 ]);
 
 @observer
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 5185d9d0e..3d898b7de 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -21,7 +21,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     }
     private get uIButtons() {
         let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
-        let curTime = NumCast(this.props.Document.curPage);
+        let curTime = NumCast(this.props.Document.currentTimecode);
         return ([<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
             <span>{"" + Math.round(curTime)}</span>
             <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
@@ -85,7 +85,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     onPointerMove = (e: PointerEvent) => {
         this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY);
         if (this._videoBox) {
-            this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.curPage, 0) + Math.sign(e.movementX) * 0.0333));
+            this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
         }
         e.stopImmediatePropagation();
     }
@@ -94,7 +94,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
         document.removeEventListener("pointermove", this.onPointerMove, true);
         document.removeEventListener("pointerup", this.onPointerUp, true);
         InkingControl.Instance.switchTool(InkTool.None);
-        this._isclick < 10 && (this.props.Document.curPage = 0);
+        this._isclick < 10 && (this.props.Document.currentTimecode = 0);
     }
     setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7b29f2c2a..bfd3e6481 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
     public getActiveDocuments = () => {
         return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bf154d37d..eaf65b88c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -285,7 +285,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
-                    d.page = -1;
+                    d.displayTimecode = undefined;
                     return d;
                 });
             }
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 2bff3ded4..b18aa5d63 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -128,7 +128,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             runInAction(async () => {
                 this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
                 this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
-                let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc;
+                let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc;
                 runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
             });
         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 88cd2cdc4..1f3887608 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -65,9 +65,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
     public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); }
     public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); }
-    public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); }
+    public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
-    public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); }
+    public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
 
     @undoBatch
     @action
@@ -128,7 +128,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                         <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div>
                 </button>
-                <input value={`${NumCast(this.props.Document.curPage)}`}
+                <input value={`${(this.Document.curPage || 1)}`}
                     onChange={e => this.gotoPage(Number(e.currentTarget.value))}
                     style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
                     onClick={action(() => this._pageControls = !this._pageControls)} />
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 573197117..e83aa8bea 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,7 +3,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
 import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface } from "../../../new_fields/Schema";
+import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
 import { RouteStore } from "../../../server/RouteStore";
@@ -16,16 +16,19 @@ import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { documentSchema } from "./DocumentView";
 import { FieldView, FieldViewProps } from './FieldView';
-import { pageSchema } from "./ImageBox";
 import "./VideoBox.scss";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faVideo } from "@fortawesome/free-solid-svg-icons";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
+import { positionSchema } from "./CollectionFreeFormDocumentView";
 var path = require('path');
 
-type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>;
-const VideoDocument = makeInterface(documentSchema, pageSchema);
+export const timeSchema = createSchema({
+    currentTimecode: "number",
+});
+type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
+const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
 
 library.add(faVideo);
 
@@ -97,11 +100,11 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action public Snapshot() {
-        let width = NumCast(this.props.Document.width);
-        let height = NumCast(this.props.Document.height);
+        let width = this.Document.width || 0;
+        let height = this.Document.height || 0;
         var canvas = document.createElement('canvas');
         canvas.width = 640;
-        canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+        canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
         var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
         if (ctx) {
             ctx.rect(0, 0, canvas.width, canvas.height);
@@ -112,24 +115,25 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
         if (!this._videoRef) { // can't find a way to take snapshots of videos
             let b = Docs.Create.ButtonDocument({
-                x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
-                width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString()
+                x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+                width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString()
             });
-            b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`);
+            b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
         } else {
             //convert to desired file format
             var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
-            let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
-            VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+            let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString()));
+            VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => {
                 if (returnedFilename) {
                     let url = this.choosePath(Utils.prepend(returnedFilename));
                     let imageSummary = Docs.Create.ImageDocument(url, {
-                        x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
-                        width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+                        x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+                        width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
                     });
+                    imageSummary.isButton = true;
                     this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
-                    DocUtils.MakeLink({doc:imageSummary}, {doc: this.props.Document}, "snapshot from " + this.props.Document.title, "video frame snapshot");
+                    DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot");
                 }
             });
         }
@@ -137,8 +141,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     @action
     updateTimecode = () => {
-        this.player && (this.props.Document.curPage = this.player.currentTime);
-        this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime());
+        this.player && (this.Document.currentTimecode = this.player.currentTime);
+        this._youtubePlayer && (this.Document.currentTimecode = this._youtubePlayer.getCurrentTime());
     }
 
     componentDidMount() {
@@ -146,12 +150,12 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
         if (this.youtubeVideoId) {
             let youtubeaspect = 400 / 315;
-            var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
-            var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+            var nativeWidth = (this.Document.nativeWidth || 0);
+            var nativeHeight = (this.Document.nativeHeight || 0);
             if (!nativeWidth || !nativeHeight) {
                 if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
                 this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect;
-                this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect;
+                this.Document.height = (this.Document.width || 0) / youtubeaspect;
             }
         }
     }
@@ -168,10 +172,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         if (vref) {
             this._videoRef!.ontimeupdate = this.updateTimecode;
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
-            if (this._reactionDisposer) this._reactionDisposer();
-            this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
-                !this.Playing && (vref.currentTime = this.Document.curPage || 0)
-                , { fireImmediately: true });
+            this._reactionDisposer && this._reactionDisposer();
+            this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
+                time => !this.Playing && (vref.currentTime = time), { fireImmediately: true });
         }
     }
 
@@ -242,7 +245,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let onYoutubePlayerReady = (event: any) => {
             this._reactionDisposer && this._reactionDisposer();
             this._youtubeReactionDisposer && this._youtubeReactionDisposer();
-            this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0));
+            this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0));
             this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
                 let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
                 iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -263,9 +266,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
         this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
         let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
-        let start = untracked(() => Math.round(NumCast(this.props.Document.curPage)));
+        let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
         return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
-            onLoad={this.youtubeIframeLoaded} className={`${style}`} width={NumCast(this.props.Document.nativeWidth, 640)} height={NumCast(this.props.Document.nativeHeight, 390)}
+            onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
             src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
         ></iframe>;
     }
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 8f64c1c2e..e381d0218 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -16,7 +16,7 @@ export interface StrokeData {
     color: string;
     width: string;
     tool: InkTool;
-    page: number;
+    displayTimecode: number;
 }
 
 export type InkData = Map<string, StrokeData>;
-- 
cgit v1.2.3-70-g09d2

