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

---
 src/client/views/collections/CollectionView.tsx | 52 ++++++++-----------------
 1 file changed, 16 insertions(+), 36 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c7ab66c9f..5819c829f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -52,39 +52,19 @@ const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
 
 export enum CollectionViewType {
-    Invalid,
-    Freeform,
-    Schema,
-    Docking,
-    Tree,
-    Stacking,
-    Masonry,
-    Multicolumn,
-    Multirow,
-    Time,
-    Carousel,
-    Linear,
-    Staff
-}
-
-export namespace CollectionViewType {
-    const stringMapping = new Map<string, CollectionViewType>([
-        ["invalid", CollectionViewType.Invalid],
-        ["freeform", CollectionViewType.Freeform],
-        ["schema", CollectionViewType.Schema],
-        ["docking", CollectionViewType.Docking],
-        ["tree", CollectionViewType.Tree],
-        ["stacking", CollectionViewType.Stacking],
-        ["masonry", CollectionViewType.Masonry],
-        ["multicolumn", CollectionViewType.Multicolumn],
-        ["multirow", CollectionViewType.Multirow],
-        ["time", CollectionViewType.Time],
-        ["carousel", CollectionViewType.Carousel],
-        ["linear", CollectionViewType.Linear],
-    ]);
-
-    export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
-    export const stringFor = (value: number) => Array.from(stringMapping.entries()).find(entry => entry[1] === value)?.[0];
+    Invalid = "invalid",
+    Freeform = "freeform",
+    Schema = "schema",
+    Docking = "docking",
+    Tree = 'tree',
+    Stacking = "stacking",
+    Masonry = "masonry",
+    Multicolumn = "multicolumn",
+    Multirow = "multirow",
+    Time = "time",
+    Carousel = "carousel",
+    Linear = "linear",
+    Staff = "staff",
 }
 
 export interface CollectionRenderProps {
@@ -110,7 +90,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     get collectionViewType(): CollectionViewType | undefined {
-        const viewField = NumCast(this.props.Document._viewType);
+        const viewField = StrCast(this.props.Document._viewType);
         if (CollectionView._safeMode) {
             if (viewField === CollectionViewType.Freeform) {
                 return CollectionViewType.Tree;
@@ -119,7 +99,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 return CollectionViewType.Freeform;
             }
         }
-        return viewField;
+        return viewField as any as CollectionViewType;
     }
 
     active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
@@ -394,7 +374,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
                 newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
             }
-            Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
+            newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From d34cb78c3d42f946be62a06bed73fe1997bb25ab Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 8 Apr 2020 01:48:02 -0400
Subject: fixed bug with getDocTemplate().  fixed inPlace link following when
 there's no inPlaceContainer.

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

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5819c829f..6e0e44d35 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -230,7 +230,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             if (this.props.Document.childDetailed instanceof Doc) {
                 layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, "onRight"), icon: "project-diagram" });
             }
-            layoutItems.push({ description: "Toggle is inPlace Container", event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+            layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset":"Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f12dd76d8..df1373b14 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,7 +4,7 @@ import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrows
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
@@ -794,7 +794,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+                if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            }
             Doc.BrushDoc(this.props.Document);
             this.props.focus(this.props.Document);
             willZoom && this.setScaleToZoom(layoutdoc, scale);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 4fc4dc1cf..a9c97fc19 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -899,7 +899,7 @@ export namespace Doc {
 
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
 Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
-Scripting.addGlobal(function getDocTemplate(doc?: any) { Doc.getDocTemplate(doc); });
+Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
-- 
cgit v1.2.3-70-g09d2


From 8895ea8bc50b9b69518469e24e66fad58b6bc2e7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 8 Apr 2020 14:38:56 -0400
Subject: fixed link previews to work with regions on PDFs, images etc,

---
 src/client/views/DocComponent.tsx                  |  2 +-
 src/client/views/collections/CollectionView.tsx    |  3 +--
 src/client/views/nodes/DocumentView.tsx            | 31 +++++++++++++---------
 src/client/views/nodes/FormattedTextBoxComment.tsx | 17 +++++++-----
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 6 files changed, 33 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0c8ce28c3..bbba2712e 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -86,7 +86,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
         }
         @action.bound
         addDocument(doc: Doc): boolean {
-            Doc.GetProto(doc).annotationOn = this.props.Document;
+            doc.context = Doc.GetProto(doc).annotationOn = this.props.Document;
             return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false;
         }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 6e0e44d35..8192e6751 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -143,7 +143,6 @@ export class CollectionView extends Touchable<FieldViewProps> {
     // moving it into the target.  
     @action.bound
     moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
-        doc.context = targetCollection;
         if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
@@ -230,7 +229,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             if (this.props.Document.childDetailed instanceof Doc) {
                 layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, "onRight"), icon: "project-diagram" });
             }
-            layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset":"Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+            layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1a33e3000..867405d54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -196,27 +196,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
         // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
 
-        !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
+        if (!this.props.dontRegisterView) {
+            DocumentManager.Instance.DocumentViews.push(this);
+        }
     }
 
     @action
     componentDidUpdate() {
-        this._dropDisposer && this._dropDisposer();
-        this._gestureEventDisposer && this._gestureEventDisposer();
-        this.multiTouchDisposer && this.multiTouchDisposer();
-        this.holdDisposer && this.holdDisposer();
-        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
-        this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
-        this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
-        this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
+        this._dropDisposer?.();
+        this._gestureEventDisposer?.();
+        this.multiTouchDisposer?.();
+        this.holdDisposer?.();
+        if (this._mainCont.current) {
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this));
+            this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
+            this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+            this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+        }
     }
 
     @action
     componentWillUnmount() {
-        this._dropDisposer && this._dropDisposer();
-        this._gestureEventDisposer && this._gestureEventDisposer();
-        this.multiTouchDisposer && this.multiTouchDisposer();
-        this.holdDisposer && this.holdDisposer();
+        this._dropDisposer?.();
+        this._gestureEventDisposer?.();
+        this.multiTouchDisposer?.();
+        this.holdDisposer?.();
         Doc.UnBrushDoc(this.props.Document);
         if (!this.props.dontRegisterView) {
             const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -982,6 +986,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             LayoutDoc={this.props.LayoutDoc}
             makeLink={this.makeLink}
             rootSelected={this.rootSelected}
+            dontRegisterView={this.props.dontRegisterView}
             fitToBox={this.props.fitToBox}
             LibraryPath={this.props.LibraryPath}
             addDocument={this.props.addDocument}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 20d734244..1e48c76e7 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -2,7 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model";
 import { EditorState, Plugin } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocCastAsync } from "../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils";
 import { DocServer } from "../../DocServer";
@@ -100,6 +100,7 @@ export class FormattedTextBoxComment {
     public static Hide() {
         FormattedTextBoxComment.textBox = undefined;
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
     }
     public static SetState(textBox: any, start: number, end: number, mark: Mark) {
         FormattedTextBoxComment.textBox = textBox;
@@ -167,14 +168,18 @@ export class FormattedTextBoxComment {
                     FormattedTextBoxComment.tooltipText.textContent = "target not found...";
                     (FormattedTextBoxComment.tooltipText as any).href = "";
                     const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
+                    try {
+                        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+                    } catch (e) { }
+                    docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
                             (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
                             FormattedTextBoxComment.linkDoc = linkDoc;
-                            const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
-                            try {
-                                ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-                            } catch (e) { }
+                            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+                            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+                            if (anchor !== target && anchor && target) {
+                                target.scrollY = NumCast(anchor?.y);
+                            }
                             if (target) {
                                 ReactDOM.render(<ContentFittingDocumentView
                                     Document={target}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 53ad547b6..f2a3e1484 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -248,7 +248,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     _pdfjsRequested = false;
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
-        if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
+        if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document.scrollY !== undefined) this._everActive = true;
         if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
             if (pdfUrl instanceof PdfField && this._pdf) {
                 return this.renderPdfView;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 90cd64f3c..f93c1fa97 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -164,7 +164,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     }
 
     componentWillUnmount = () => {
-        this._reactionDisposer && this._reactionDisposer();
+        this._reactionDisposer?.();
         this._scrollTopReactionDisposer?.();
         this._annotationReactionDisposer?.();
         this._filterReactionDisposer?.();
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 348e794e7ff341a4358be6ed9515fc6ae723092e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 12 Apr 2020 21:36:26 -0700
Subject: rough pass at collection map view

---
 package-lock.json                                  | 68 +++++++++++++++-----
 package.json                                       |  6 +-
 src/client/Network.ts                              | 13 ++++
 src/client/cognitive_services/CognitiveServices.ts |  1 -
 src/client/documents/Documents.ts                  |  9 ++-
 src/client/views/Main.tsx                          |  1 -
 .../views/collections/CollectionMapView.scss       |  4 ++
 src/client/views/collections/CollectionMapView.tsx | 72 ++++++++++++++++++++++
 src/client/views/collections/CollectionView.tsx    | 10 ++-
 src/new_fields/Types.ts                            |  1 +
 webpack.config.js                                  | 55 ++++++++++-------
 11 files changed, 195 insertions(+), 45 deletions(-)
 create mode 100644 src/client/views/collections/CollectionMapView.scss
 create mode 100644 src/client/views/collections/CollectionMapView.tsx

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

diff --git a/package-lock.json b/package-lock.json
index 1949a8a5c..f40dc7b0f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -584,6 +584,20 @@
                 "@types/node": "*"
             }
         },
+        "@types/google-maps-react": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/@types/google-maps-react/-/google-maps-react-2.0.4.tgz",
+            "integrity": "sha512-8EGr84L6ozODnfjWN9xVUFIxFedBbZinuDe5lBEJ757yh/lZqoKkqxrYLqvrLksDRVVwQYwWdFvfEASujmk36A==",
+            "requires": {
+                "@types/googlemaps": "*",
+                "@types/react": "*"
+            }
+        },
+        "@types/googlemaps": {
+            "version": "3.39.3",
+            "resolved": "https://registry.npmjs.org/@types/googlemaps/-/googlemaps-3.39.3.tgz",
+            "integrity": "sha512-L8O9HAVFZj0TuiS8h5ORthiMsrrhjxTC8XUusp5k47oXCst4VTm+qWKvrAvmYMybZVokbp4Udco1mNwJrTNZPQ=="
+        },
         "@types/isstream": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/@types/isstream/-/isstream-0.1.0.tgz",
@@ -750,7 +764,7 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
                 "@types/express": "*"
@@ -5615,7 +5629,8 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5633,11 +5648,13 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5650,15 +5667,18 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5761,7 +5781,8 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5771,6 +5792,7 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5783,17 +5805,20 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "minipass": {
                     "version": "2.9.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5810,6 +5835,7 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5890,7 +5916,8 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5900,6 +5927,7 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -5975,7 +6003,8 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -6005,6 +6034,7 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -6022,6 +6052,7 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -6060,11 +6091,13 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "yallist": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 }
             }
         },
@@ -6344,6 +6377,11 @@
                 }
             }
         },
+        "google-maps-react": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/google-maps-react/-/google-maps-react-2.0.2.tgz",
+            "integrity": "sha512-6cYauGwt22haDUrWxKQ6yoNOqjiuxHo8YYcmb+aBvNICokdXmZOUB6Ah4vD5VexMVlrwP2PFqA/D8sHpEB52KA=="
+        },
         "google-p12-pem": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.1.tgz",
@@ -14376,7 +14414,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -15990,7 +16028,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -18303,7 +18341,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/package.json b/package.json
index 440646c48..c742857ad 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
     },
     "scripts": {
         "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
-        "start": "cross-env HANDWRITING='61088486d76c4b12ba578775a5f55422' NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+        "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
         "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts",
         "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
         "test": "mocha -r ts-node/register test/**/*.ts",
@@ -83,6 +83,7 @@
         "@types/express-validator": "^3.0.0",
         "@types/formidable": "^1.0.31",
         "@types/gapi": "0.0.39",
+        "@types/google-maps-react": "^2.0.4",
         "@types/jquery": "^3.3.31",
         "@types/jquery-awesome-cursor": "^0.3.0",
         "@types/jsonwebtoken": "^8.3.7",
@@ -162,6 +163,7 @@
         "formidable": "^1.2.1",
         "golden-layout": "^1.5.9",
         "google-auth-library": "^4.2.4",
+        "google-maps-react": "^2.0.2",
         "googleapis": "^40.0.0",
         "googlephotos": "^0.2.5",
         "howler": "^2.1.3",
@@ -258,4 +260,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/Network.ts b/src/client/Network.ts
index 6982ecf19..bd0e6e61a 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -4,10 +4,23 @@ import { Upload } from "../server/SharedMediaTypes";
 
 export namespace Networking {
 
+    const EnvVarCache = new Map<string, string>();
+
     export async function FetchFromServer(relativeRoute: string) {
         return (await fetch(relativeRoute)).text();
     }
 
+    export async function FetchEnvironmentVariable(varNameLiteral: string) {
+        let resolved = EnvVarCache.get(varNameLiteral);
+        if (!resolved) {
+            resolved = await FetchFromServer(`/environment/${varNameLiteral}`);
+            if (resolved !== undefined) {
+                EnvVarCache.set(varNameLiteral, resolved);
+            }
+        }
+        return resolved;
+    }
+
     export async function PostToServer(relativeRoute: string, body?: any) {
         const options = {
             uri: Utils.prepend(relativeRoute),
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 5d83de233..3133bf4b1 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -8,7 +8,6 @@ import { UndoManager } from "../util/UndoManager";
 import requestPromise = require("request-promise");
 import { List } from "../../new_fields/List";
 import { ClientRecommender } from "../ClientRecommender";
-import { ImageBox } from "../views/nodes/ImageBox";
 
 type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor };
 type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 703c049cd..43e379125 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -49,6 +49,8 @@ import { ContextMenuProps } from "../views/ContextMenuItem";
 import { ContextMenu } from "../views/ContextMenu";
 import { LinkBox } from "../views/nodes/LinkBox";
 import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
+import CollectionMapView from "../views/collections/CollectionMapView";
+import LocationField, { LocationData } from "../../new_fields/LocationField";
 const requestImageSize = require('../util/request-image-size');
 const path = require('path');
 
@@ -277,8 +279,7 @@ export namespace Docs {
             }],
             [DocumentType.SCREENSHOT, {
                 layout: { view: ScreenshotBox, dataField: data },
-                options: {}
-            }]
+            }],
         ]);
 
         // All document prototypes are initialized with at least these values
@@ -624,6 +625,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
         }
 
+        export function MapDocument(documents: Array<Doc>, options: DocumentOptions = {}) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), options);
+        }
+
         export function CarouselDocument(documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Carousel });
         }
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 6d705aa44..b21eb9c8f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,7 +5,6 @@ import * as ReactDOM from 'react-dom';
 import * as React from 'react';
 import { DocServer } from "../DocServer";
 import { AssignAllExtensions } from "../../extensions/General/Extensions";
-process.env.HANDWRITING = "61088486d76c4b12ba578775a5f55422";
 
 AssignAllExtensions();
 
diff --git a/src/client/views/collections/CollectionMapView.scss b/src/client/views/collections/CollectionMapView.scss
new file mode 100644
index 000000000..c74433902
--- /dev/null
+++ b/src/client/views/collections/CollectionMapView.scss
@@ -0,0 +1,4 @@
+.collectionMapView-contents {
+    width: 100%;
+    height: 100%;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
new file mode 100644
index 000000000..49411f61b
--- /dev/null
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -0,0 +1,72 @@
+import { observer } from "mobx-react";
+import { makeInterface } from "../../../new_fields/Schema";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import React = require("react");
+import { Map, Marker, MapProps, GoogleApiWrapper } from "google-maps-react";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { CollectionSubView } from "./CollectionSubView";
+import { Utils } from "../../../Utils";
+
+type MapDocument = makeInterface<[typeof documentSchema]>;
+const MapDocument = makeInterface(documentSchema);
+
+export type LocationData = google.maps.LatLngLiteral & { address?: string };
+
+@observer
+class CollectionMapView extends CollectionSubView<MapDocument, Partial<MapProps> & { google: any }>(MapDocument) {
+
+    render() {
+        const { childLayoutPairs, props } = this;
+        const { Document } = props;
+        const center: LocationData = { lat: NumCast(Document.mapCenterLat), lng: NumCast(Document.mapCenterLng) };
+        if (!center.lat) {
+            center.lat = childLayoutPairs.length ? NumCast(childLayoutPairs[0].layout.locationLat, 0) : 0;
+            center.lng = childLayoutPairs.length ? NumCast(childLayoutPairs[0].layout.locationLng, 0) : 0;
+        }
+        return (
+            <div
+                className={"collectionMapView-contents"}
+            >
+                <Map
+                    {...props}
+                    zoom={NumCast(Document.zoom, 10)}
+                    center={center}
+                    initialCenter={center}
+                >
+                    {childLayoutPairs.map(({ layout }) => {
+                        const location: LocationData = {
+                            lat: NumCast(childLayoutPairs[0].layout.locationLat, 0),
+                            lng: NumCast(childLayoutPairs[0].layout.locationLng, 0)
+                        };
+                        const iconSize = new google.maps.Size(NumCast(layout.mapIconWidth, 45), NumCast(layout.mapIconHeight, 45));
+                        return (
+                            <Marker
+                                key={Utils.GenerateGuid()}
+                                label={StrCast(layout.title)}
+                                position={location}
+                                onClick={() => {
+                                    Document.mapCenterLat = location.lat;
+                                    Document.mapCenterLng = location.lng;
+                                }}
+                                icon={{
+                                    size: iconSize,
+                                    scaledSize: iconSize,
+                                    url: StrCast(Document.mapIconUrl, "https://www.pinclipart.com/picdir/middle/359-3598915_map-marker-icon-location-icon-png-clipart.png")
+                                }}
+                            />
+                        );
+                    })}
+                </Map>
+            </div>
+        );
+    }
+
+}
+
+declare var process: {
+    env: {
+        GOOGLE_MAPS_API_KEY: string;
+    }
+};
+
+export default GoogleApiWrapper({ apiKey: process.env.GOOGLE_MAPS_API_KEY })(CollectionMapView) as any;
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 821840e0b..d1dc32829 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,7 +1,7 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEye, faEdit } from '@fortawesome/free-regular-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
+import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree, faGlobeAmericas } from '@fortawesome/free-solid-svg-icons';
 import { action, observable, computed } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
@@ -44,12 +44,15 @@ import { Docs } from '../../documents/Documents';
 import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
+import CollectionMapView from './CollectionMapView';
+import { ClientUtils } from '../../util/ClientUtils';
+import { GoogleApiWrapper } from 'google-maps-react';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
-library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
+library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faGlobeAmericas, faEllipsisV, faImage, faEye as any, faCopy);
 
 export enum CollectionViewType {
     Invalid = "invalid",
@@ -65,6 +68,7 @@ export enum CollectionViewType {
     Carousel = "carousel",
     Linear = "linear",
     Staff = "staff",
+    Map = "map"
 }
 
 export interface CollectionRenderProps {
@@ -170,6 +174,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); }
+            case CollectionViewType.Map: return (<CollectionMapView key="collview" {...props} />);
             case CollectionViewType.Freeform:
             default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
         }
@@ -211,6 +216,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
             subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" });
             subItems.push({ description: "Pivot/Time", event: () => this.props.Document._viewType = CollectionViewType.Time, icon: "columns" });
+            subItems.push({ description: "Map", event: () => this.props.Document._viewType = CollectionViewType.Map, icon: "globe-americas" });
             switch (this.props.Document._viewType) {
                 case CollectionViewType.Freeform: {
                     subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 0ca35fab2..aa44cefa0 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -87,6 +87,7 @@ export function BoolCast(field: FieldResult, defaultVal: boolean | null = false)
 export function DateCast(field: FieldResult) {
     return Cast(field, DateField, null);
 }
+
 export function ScriptCast(field: FieldResult) {
     return Cast(field, ScriptField, null);
 }
diff --git a/webpack.config.js b/webpack.config.js
index 6a14dfcda..655334ef2 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,6 +2,14 @@ var path = require('path');
 var webpack = require('webpack');
 const CopyWebpackPlugin = require("copy-webpack-plugin");
 const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
+const env = require('dotenv').config().parsed;
+const envKeys = Object.keys(env).reduce((prev, next) => {
+    if (next.startsWith("DASH_")) {
+        const resolved = next.replace("DASH_", "");
+        prev[`process.env.${resolved}`] = JSON.stringify(env[next]);
+    }
+    return prev;
+}, {});
 
 module.exports = {
     mode: 'development',
@@ -33,17 +41,18 @@ module.exports = {
         extensions: ['.js', '.ts', '.tsx']
     },
     module: {
-        rules: [
-            {
+        rules: [{
                 test: [/\.tsx?$/],
-                use: [
-                    { loader: 'ts-loader', options: { transpileOnly: true } }
-                ]
+                use: [{
+                    loader: 'ts-loader',
+                    options: {
+                        transpileOnly: true
+                    }
+                }]
             },
             {
                 test: /\.scss|css$/,
-                use: [
-                    {
+                use: [{
                         loader: "style-loader"
                     },
                     {
@@ -56,28 +65,30 @@ module.exports = {
             },
             {
                 test: /\.(jpg|png|pdf)$/,
-                use: [
-                    {
-                        loader: 'file-loader'
-                    }
-                ]
+                use: [{
+                    loader: 'file-loader'
+                }]
             },
             {
                 test: /\.(png|jpg|gif)$/i,
-                use: [
-                    {
-                        loader: 'url-loader',
-                        options: {
-                            limit: 8192
-                        }
+                use: [{
+                    loader: 'url-loader',
+                    options: {
+                        limit: 8192
                     }
-                ]
-            }]
+                }]
+            }
+        ]
     },
     plugins: [
-        new CopyWebpackPlugin([{ from: "deploy", to: path.join(__dirname, "build") }]),
+        new CopyWebpackPlugin([{
+            from: "deploy",
+            to: path.join(__dirname, "build")
+        }]),
+        new webpack.DefinePlugin(envKeys),
         new ForkTsCheckerWebpackPlugin({
-            tslint: true, useTypescriptIncrementalApi: true
+            tslint: true,
+            useTypescriptIncrementalApi: true
         }),
         new webpack.optimize.OccurrenceOrderPlugin(),
         new webpack.HotModuleReplacementPlugin(),
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 87268612b9b765d74f1a5317b0894be0ceb1dfd1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Apr 2020 16:43:50 -0400
Subject: fixed map view pointer events.

---
 src/client/views/collections/CollectionMapView.scss | 3 +++
 src/client/views/collections/CollectionMapView.tsx  | 8 ++++----
 src/client/views/collections/CollectionView.tsx     | 2 +-
 3 files changed, 8 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionMapView.scss b/src/client/views/collections/CollectionMapView.scss
index c74433902..df7853da6 100644
--- a/src/client/views/collections/CollectionMapView.scss
+++ b/src/client/views/collections/CollectionMapView.scss
@@ -1,4 +1,7 @@
 .collectionMapView-contents {
     width: 100%;
     height: 100%;
+}
+.collectionMapView-contents-none {
+    pointer-events: none;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index b67daeb53..5075bbf7a 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -7,6 +7,7 @@ import { NumCast, StrCast } from "../../../new_fields/Types";
 import { CollectionSubView } from "./CollectionSubView";
 import { Utils } from "../../../Utils";
 import { Opt } from "../../../new_fields/Doc";
+import "./CollectionMapView.scss";
 
 type MapDocument = makeInterface<[typeof documentSchema]>;
 const MapDocument = makeInterface(documentSchema);
@@ -25,11 +26,10 @@ class CollectionMapView extends CollectionSubView<MapDocument, Partial<MapProps>
             center.lng = childLayoutPairs.length ? NumCast(childLayoutPairs[0].layout.locationLng, 0) : 0;
         }
         return (
-            <div
-                className={"collectionMapView-contents"}
-            >
+            <div className={"collectionMapView-contents" + (this.props.active() ? "" : "-none")}
+                onPointerDown={e => (this.props.active() && e.button === 0 && !e.ctrlKey) && e.stopPropagation()} >
                 <Map
-                    {...props}
+                    google={this.props.google}
                     zoom={NumCast(Document.zoom, 10)}
                     center={center}
                     initialCenter={center}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4d04f2050..8cc5146d5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -106,7 +106,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField as any as CollectionViewType;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.props.Document.forceActive || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => (this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.props.Document.forceActive || this._isChildActive || this.props.renderDepth === 0) ? true : false;
 
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
-- 
cgit v1.2.3-70-g09d2


From 130e26103461f23a2d8b57ea2eef6b17eefb8f8e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Apr 2020 21:46:20 -0400
Subject: updated childDetailedView stuff for pivot view

---
 src/client/views/TemplateMenu.tsx                      |  4 ++--
 src/client/views/collections/CollectionTimeView.tsx    | 10 ++++++----
 src/client/views/collections/CollectionTreeView.tsx    |  2 +-
 src/client/views/collections/CollectionView.tsx        |  4 ++--
 src/client/views/collections/CollectionViewChromes.tsx |  4 ++--
 src/client/views/nodes/DocumentView.tsx                |  8 ++++----
 src/new_fields/Doc.ts                                  |  8 --------
 src/new_fields/documentSchemas.ts                      |  2 +-
 8 files changed, 18 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 4d7f1e443..b76137f06 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -167,8 +167,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     }
 }
 
-Scripting.addGlobal(function switchView(doc: Doc, template: Doc) {
-    if (template.dragFactory) {
+Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
+    if (template?.dragFactory) {
         template = Cast(template.dragFactory, Doc, null);
     }
     const templateTitle = StrCast(template?.title);
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 53de2fbbe..31b720b81 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -5,7 +5,7 @@ import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { NumCast, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
@@ -19,6 +19,7 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
+import { DocumentView } from "../nodes/DocumentView";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -28,14 +29,15 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     componentWillUnmount() {
         this.props.Document.onChildClick = undefined;
     }
-    componentDidMount() {
-        const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
-        const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(thisContainer.childDetailed, alias, 'layout_detailView'); alias.layoutKey='layout_detailedView'; alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+    async componentDidMount() {
+        const childText = "const alias = getAlias(this); switchView(alias, thisContainer.childDetailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
         this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", thisContainer: Doc.name, shiftKey: "boolean" });
         this.props.Document._fitToBox = true;
         if (!this.props.Document.onViewDefClick) {
             this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
         }
+        this.props.Document.childDetailView = Cast(this.props.Document.childDetailView, Doc, null) ||// bcz: needs to be here to make sure the childDetailView layout template has been loaded when the first item is clicked;
+            DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
     }
 
     layoutEngine = () => this._layoutEngine;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 23fb259fc..b96ee4bc4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -754,7 +754,7 @@ export class CollectionTreeView extends CollectionSubView(Document, undefined as
                     }));
 
                 Document.childLayout = heroView;
-                Document.childDetailed = detailView;
+                Document.childDetailView = detailView;
                 Document._viewType = CollectionViewType.Time;
                 Document._forceActive = true;
                 Document._pivotField = "company";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8cc5146d5..f94277862 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -232,8 +232,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
             if (this.props.Document.childLayout instanceof Doc) {
                 layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
             }
-            if (this.props.Document.childDetailed instanceof Doc) {
-                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, "onRight"), icon: "project-diagram" });
+            if (this.props.Document.childDetailView instanceof Doc) {
+                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailView as Doc, "onRight"), icon: "project-diagram" });
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index ba95dce00..e494c337a 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -48,8 +48,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     };
     _narrativeCommand = {
         params: ["target", "source"], title: "=> click item view",
-        script: "this.target.childDetailed = getDocTemplate(this.source?.[0])",
-        immediate: (source: Doc[]) => this.target.childDetailed = Doc.getDocTemplate(source?.[0]),
+        script: "this.target.childDetailView = getDocTemplate(this.source?.[0])",
+        immediate: (source: Doc[]) => this.target.childDetailView = Doc.getDocTemplate(source?.[0]),
         initialize: emptyFunction,
     };
     _contentCommand = {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc3c39154..5b6478e66 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -541,7 +541,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
 
     // applies a custom template to a document.  the template is identified by it's short name (e.g, slideView not layout_slideView)
-    static makeCustomViewClicked = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+    static makeCustomViewClicked = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
         const batch = UndoManager.StartBatch("makeCustomViewClicked");
         runInAction(() => {
             doc.layoutKey = "layout_" + templateSignature;
@@ -563,7 +563,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
         return docLayoutTemplate;
     }
-    static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+    static createCustomView = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
         const templateName = templateSignature.replace(/\(.*\)/, "");
         docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, StrCast(doc.type), templateSignature);
 
@@ -584,10 +584,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         } else if (doc.data instanceof ImageField) {
             fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
         }
-        const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+        const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
 
         fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));
-        Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+        docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
     }
 
     @undoBatch
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 92abb7a71..bcf0d1aec 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -603,14 +603,6 @@ export namespace Doc {
         return undefined;
     }
     export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) {
-        if (!templateDoc) {
-            target.layout = undefined;
-            target._nativeWidth = undefined;
-            target._nativeHeight = undefined;
-            target.type = undefined;
-            return;
-        }
-
         if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
             if (target.resolvedDataDoc) {
                 target[targetKey] = new PrefetchProxy(templateDoc);
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index b11941f40..bc63e9df8 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -74,7 +74,7 @@ export const positionSchema = createSchema({
 
 export const collectionSchema = createSchema({
     childLayout: Doc, // layout template for children of a collecion
-    childDetailed: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field)
+    childDetailView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field)
     onChildClick: ScriptField, // script to run for each child when its clicked
     onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
 });
-- 
cgit v1.2.3-70-g09d2


From a9613d73947ee9b000c695b8f3d7685feed96a27 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Apr 2020 17:37:20 -0400
Subject: fixed goldenlayout borders.  made filter flyout semistransparent.
 fixed map to stay inbounds when filter is expanded.

---
 src/client/goldenLayout.js                          | 4 +++-
 src/client/views/collections/CollectionMapView.scss | 3 +++
 src/client/views/collections/CollectionView.scss    | 1 -
 src/client/views/collections/CollectionView.tsx     | 2 +-
 4 files changed, 7 insertions(+), 3 deletions(-)

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

diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index b510385ff..2d4283b02 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -1551,7 +1551,7 @@
         },
         dimensions: {
             borderWidth: 5,
-            borderGrabWidth: 15,
+            borderGrabWidth: 5,
             minItemHeight: 10,
             minItemWidth: 10,
             headerHeight: 20,
@@ -2796,11 +2796,13 @@
             if (this._isVertical) {
                 dragHandle.css('top', -handleExcessPos);
                 dragHandle.css('height', this._size + handleExcessSize);
+                element.css('cursor', 'row-resize');
                 element.addClass('lm_vertical');
                 element['height'](this._size);
             } else {
                 dragHandle.css('left', -handleExcessPos);
                 dragHandle.css('width', this._size + handleExcessSize);
+                element.css('cursor', 'col-resize');
                 element.addClass('lm_horizontal');
                 element['width'](this._size);
             }
diff --git a/src/client/views/collections/CollectionMapView.scss b/src/client/views/collections/CollectionMapView.scss
index cb58b1750..7e4f9a0f4 100644
--- a/src/client/views/collections/CollectionMapView.scss
+++ b/src/client/views/collections/CollectionMapView.scss
@@ -4,6 +4,9 @@
     .collectionMapView-contents {
         width: 100%;
         height: 100%;
+        > div {
+            position: unset !important; // when the sidebar filter flys out, this prevents the map from extending outside the document box
+        }
     }
 }
 
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index b92c5fdd1..a0b73b90f 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -11,7 +11,6 @@
     height: 100%;
     overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
 
-
     .collectionTimeView-dragger {
         background-color: lightgray;
         height: 40px;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f94277862..13a657800 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -389,7 +389,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             return false;
         }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
     }
-    filterBackground = () => "dimGray";
+    filterBackground = () => "rgba(105, 105, 105, 0.432)";
     get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
     @computed get scriptField() {
         const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
-- 
cgit v1.2.3-70-g09d2


From b9483279f4a778e0cc997e304b4f0b87e6ec1e5d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Apr 2020 00:29:00 -0400
Subject: changed collection chrome filter to just open filter sidebar.  fixed
 scrollbar to make sure that all datapoints are included.  simplified filter
 documents to have only a datadocument.

---
 src/client/documents/Documents.ts                  |   1 -
 .../views/collections/CollectionTreeView.tsx       |  11 +-
 src/client/views/collections/CollectionView.tsx    |  20 +-
 .../views/collections/CollectionViewChromes.scss   |   3 +-
 .../views/collections/CollectionViewChromes.tsx    | 316 ++++++---------------
 5 files changed, 100 insertions(+), 251 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e6f3b21ca..129ab0403 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -142,7 +142,6 @@ export interface DocumentOptions {
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
     treeViewOpen?: boolean; // whether this document is expanded in a tree view
     treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
-    isFacetFilter?: boolean; // whether document functions as a facet filter in a tree view
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b96ee4bc4..9ec8c4c5b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -840,12 +840,11 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
         }
     });
     const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue =>
-        Docs.Create.TextDocument("", {
-            title: facetValue.toString(),
-            treeViewChecked: ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)",
-                {},
-                { layoutDoc, facetHeader, facetValue })
-        }));
+        { const doc = new Doc();
+            doc.title = facetValue.toString();
+            doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)",{},{ layoutDoc, facetHeader, facetValue });
+            return doc;
+        });
     return new List<Doc>(facetValueDocSet);
 });
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 13a657800..831691f8c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -45,8 +45,6 @@ import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import CollectionMapView from './CollectionMapView';
-import { ClientUtils } from '../../util/ClientUtils';
-import { GoogleApiWrapper } from 'google-maps-react';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -279,7 +277,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
             onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
             onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
     }
-    @observable _facetWidth = 0;
+    get _facetWidth() { return NumCast(this.props.Document._facetWidth) }
+    set _facetWidth(value) { this.props.Document._facetWidth = value; }
 
     bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
     getTransform = () => this.props.ScreenToLocalTransform().translate(-this.facetWidth(), 0);
@@ -365,17 +364,22 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
                 newFacet.treeViewExpandedView = "layout";
                 newFacet.treeViewOpen = true;
-                newFacet[newFacetField + "-min"] = ranged === undefined ? minVal : ranged[0];
-                newFacet[newFacetField + "-max"] = ranged === undefined ? maxVal : ranged[1];
-                Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = minVal;
-                Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = maxVal;
+                const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05);
+                const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05);
+                newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
+                newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
+                Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal;
+                Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
                 newFacet.target = this.props.Document;
                 const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
                 newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
 
                 Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
             } else {
-                newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+                newFacet = new Doc();
+                newFacet.title = facetHeader;
+                newFacet.treeViewOpen = true;
+                newFacet.type = DocumentType.COL;
                 const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
                 newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables);
             }
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index a691b4805..9529ef525 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -67,10 +67,11 @@
                 color:grey;
                 margin-top:auto;
                 margin-bottom:auto;
+                margin-left: 5px;
             }
 
             .collectionViewBaseChrome-viewSpecs {
-                margin-left: 10px;
+                margin-left: 25px;
                 display: grid;
                 
                 .collectionViewBaseChrome-filterIcon {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 66f627083..cef85e148 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -6,7 +6,6 @@ import { Doc, DocListCast } 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 { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
 import { DragManager } from "../../util/DragManager";
@@ -16,8 +15,6 @@ import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
 import { CollectionViewType } from "./CollectionView";
 import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
-import * as Autosuggest from 'react-autosuggest';
-import KeyRestrictionRow from "./KeyRestrictionRow";
 const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
@@ -84,62 +81,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     private _picker: any;
     private _commandRef = React.createRef<HTMLInputElement>();
     private _viewRef = React.createRef<HTMLInputElement>();
-    private _autosuggestRef = React.createRef<Autosuggest>();
     @observable private _currentKey: string = "";
-    @observable private _viewSpecsOpen: boolean = false;
-    @observable private _dateWithinValue: string = "";
-    @observable private _dateValue: Date | string = "";
-    @observable private _keyRestrictions: [JSX.Element, string][] = [];
-    @observable private suggestions: string[] = [];
-    @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
-
-    getFilters = (script: string) => {
-        const re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g;
-        const arr: any[] = re.exec(script);
-        const toReturn: Filter[] = [];
-        if (arr !== null) {
-            const filter: Filter = {
-                key: arr[2],
-                value: arr[3],
-                contains: (arr[1] === "!") ? false : true,
-            };
-            toReturn.push(filter);
-            script = script.replace(arr[0], "");
-            if (re.exec(script) !== null) {
-                toReturn.push(...this.getFilters(script));
-            }
-            else { return toReturn; }
-        }
-        return toReturn;
-    }
-
-    addKeyRestrictions = (fields: Filter[]) => {
-
-        if (fields.length !== 0) {
-            for (let i = 0; i < fields.length; i++) {
-                this._keyRestrictions.push([<KeyRestrictionRow field={fields[i].key} value={fields[i].value} key={Utils.GenerateGuid()} contains={fields[i].contains} script={(value: string) => runInAction(() => this._keyRestrictions[i][1] = value)} />, ""]);
-
-            }
-            if (this._keyRestrictions.length === 1) {
-                this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
-            }
-        }
-        else {
-            this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]);
-            this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
-        }
-    }
 
     componentDidMount = () => {
-
-        let fields: Filter[] = [];
-        if (this.filterValue) {
-            const string = this.filterValue.script.originalScript;
-            fields = this.getFilters(string);
-        }
-
         runInAction(() => {
-            this.addKeyRestrictions(fields);
             // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
             const chromeStatus = this.props.CollectionView.props.Document._chromeStatus;
             if (chromeStatus) {
@@ -158,7 +103,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
         //@ts-ignore
-        this.props.CollectionView.props.Document._viewType = e.target.selectedOptions[0].value;
+        this.document._viewType = e.target.selectedOptions[0].value;
     }
 
     commandChanged = (e: React.ChangeEvent) => {
@@ -167,104 +112,93 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     }
 
     @action
-    openViewSpecs = (e: React.SyntheticEvent) => {
-        if (this._viewSpecsOpen) this.closeViewSpecs();
-        else {
-            this._viewSpecsOpen = true;
-
-            //@ts-ignore
-            if (!e.target?.classList[0]?.startsWith("qs")) {
-                this.closeDatePicker();
-            }
-
-            e.stopPropagation();
-            document.removeEventListener("pointerdown", this.closeViewSpecs);
-            document.addEventListener("pointerdown", this.closeViewSpecs);
-        }
+    toggleViewSpecs = (e: React.SyntheticEvent) => {
+        this.document._facetWidth = this.document._facetWidth ? 0 : 200;
+        e.stopPropagation();
     }
 
     @action closeViewSpecs = () => {
-        this._viewSpecsOpen = false;
-        document.removeEventListener("pointerdown", this.closeViewSpecs);
-    }
-
-    @action
-    openDatePicker = (e: React.PointerEvent) => {
-        this.openViewSpecs(e);
-        if (this._picker) {
-            this._picker.alwaysShow = true;
-            this._picker.show();
-            // TODO: calendar is offset when zoomed in/out
-            // this._picker.calendar.style.position = "absolute";
-            // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
-            // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
-            // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
-            // this._picker.calendar.style.left = x;
-            // this._picker.calendar.style.top = y;
-
-            e.stopPropagation();
-        }
-    }
-
-    @action
-    addKeyRestriction = (e: React.MouseEvent) => {
-        const index = this._keyRestrictions.length;
-        this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[index][1] = value)} />, ""]);
-
-        this.openViewSpecs(e);
-    }
-
-    @action.bound
-    applyFilter = (e: React.MouseEvent) => {
-
-        this.openViewSpecs(e);
-
-        const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
-        const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
-        const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
-        const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
-        const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
-        let dateRestrictionScript = "";
-        if (this._dateValue instanceof Date) {
-            const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
-            const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
-            dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
-        }
-        else {
-            const createdDate = new Date(this._dateValue);
-            if (!isNaN(createdDate.getTime())) {
-                const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
-                const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
-                dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
-            }
-        }
-        const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
-            `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
-            `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
-            "true";
+        this.document._facetWidth = 0;
+    }
+
+    // @action
+    // openDatePicker = (e: React.PointerEvent) => {
+    //     if (this._picker) {
+    //         this._picker.alwaysShow = true;
+    //         this._picker.show();
+    //         // TODO: calendar is offset when zoomed in/out
+    //         // this._picker.calendar.style.position = "absolute";
+    //         // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
+    //         // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
+    //         // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
+    //         // this._picker.calendar.style.left = x;
+    //         // this._picker.calendar.style.top = y;
+
+    //         e.stopPropagation();
+    //     }
+    // }
+
+    // <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+    //     id={Utils.GenerateGuid()}
+    //     ref={this.datePickerRef}
+    //     value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
+    //     onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
+    //     onPointerDown={this.openDatePicker}
+    //     placeholder="Value" />
+    // @action.bound
+    // applyFilter = (e: React.MouseEvent) => {
+    //     const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
+    //     const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
+    //     const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
+    //     const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
+    //     const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
+    //     let dateRestrictionScript = "";
+    //     if (this._dateValue instanceof Date) {
+    //         const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
+    //         const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
+    //         dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
+    //     }
+    //     else {
+    //         const createdDate = new Date(this._dateValue);
+    //         if (!isNaN(createdDate.getTime())) {
+    //             const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
+    //             const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
+    //             dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
+    //         }
+    //     }
+    //     const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
+    //         `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
+    //         `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
+    //         "true";
+
+    //     this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
+    // }
+
+    // datePickerRef = (node: HTMLInputElement) => {
+    //     if (node) {
+    //         try {
+    //             this._picker = datepicker("#" + node.id, {
+    //                 disabler: (date: Date) => date > new Date(),
+    //                 onSelect: (instance: any, date: Date) => runInAction(() => {}), //  this._dateValue = date),
+    //                 dateSelected: new Date()
+    //             });
+    //         } catch (e) {
+    //             console.log("date picker exception:" + e);
+    //         }
+    //     }
+    // }
 
-        this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
-    }
-
-    @action
-    closeDatePicker = () => {
-        if (this._picker) {
-            this._picker.alwaysShow = false;
-            this._picker.hide();
-        }
-        document.removeEventListener("pointerdown", this.closeDatePicker);
-    }
 
     @action
     toggleCollapse = () => {
-        this.props.CollectionView.props.Document._chromeStatus = this.props.CollectionView.props.Document._chromeStatus === "enabled" ? "collapsed" : "enabled";
+        this.document._chromeStatus = this.document._chromeStatus === "enabled" ? "collapsed" : "enabled";
         if (this.props.collapse) {
             this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
         }
     }
 
     subChrome = () => {
-        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+        const collapsed = this.document._chromeStatus !== "enabled";
         if (collapsed) return null;
         switch (this.props.type) {
             case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
@@ -279,13 +213,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         return this.props.CollectionView.props.Document;
     }
 
-    @action.bound
-    clearFilter = () => {
-        this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name });
-        this._keyRestrictions = [];
-        this.addKeyRestrictions([]);
-    }
-
     private dropDisposer?: DragManager.DragDropDisposer;
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer && this.dropDisposer();
@@ -304,48 +231,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         return true;
     }
 
-    datePickerRef = (node: HTMLInputElement) => {
-        if (node) {
-            try {
-                this._picker = datepicker("#" + node.id, {
-                    disabler: (date: Date) => date > new Date(),
-                    onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
-                    dateSelected: new Date()
-                });
-            } catch (e) {
-                console.log("date picker exception:" + e);
-            }
-        }
-    }
-
-    renderSuggestion = (suggestion: string) => {
-        return <p>{suggestion}</p>;
-    }
-    getSuggestionValue = (suggestion: string) => suggestion;
-
-    @action
-    onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
-        this._currentKey = newValue;
-    }
-    onSuggestionFetch = async ({ value }: { value: string }) => {
-        const sugg = await this.getKeySuggestions(value);
-        runInAction(() => this.suggestions = sugg);
-    }
-    @action
-    onSuggestionClear = () => {
-        this.suggestions = [];
-    }
-    getKeySuggestions = async (value: string): Promise<string[]> => {
-        return this._buttonizableCommands.filter(c => c.title.indexOf(value) !== -1).map(c => c.title);
-    }
-
-    autoSuggestDown = (e: React.PointerEvent) => {
-        e.stopPropagation();
-    }
-
-    private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
-    private _sensitivity: number = 16;
-
     dragViewDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e, down, delta) => {
             const vtype = this.props.CollectionView.collectionViewType;
@@ -367,7 +252,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                     { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
             return true;
         }, emptyFunction, emptyFunction);
-        this._startDragPosition = { x: e.clientX, y: e.clientY };
     }
 
     render() {
@@ -392,7 +276,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             title="Collapse collection chrome" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <div className="collectionViewBaseChrome-template" style={{ marginLeft: 25, display: collapsed ? "none" : undefined }}>
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
+                        </div>
+                        <div className="collectionViewBaseChrome-template" style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
                                 <div className="commandEntry-drop">
                                     <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
@@ -414,49 +303,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 </select>
                             </div>
                         </div>
-                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
-                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} >
-                                <FontAwesomeIcon icon="filter" size="2x" />
-                            </div>
-                            <div className="collectionViewBaseChrome-viewSpecsMenu"
-                                onPointerDown={this.openViewSpecs}
-                                style={{
-                                    height: this._viewSpecsOpen ? "fit-content" : "0px",
-                                    overflow: this._viewSpecsOpen ? "initial" : "hidden"
-                                }}>
-                                {this._keyRestrictions.map(i => i[0])}
-                                <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                                    <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
-                                        CREATED WITHIN:
-                                    </div>
-                                    <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
-                                        style={{ textTransform: "uppercase", textAlign: "center" }}
-                                        value={this._dateWithinValue}
-                                        onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
-                                        <option value="1d">1 day of</option>
-                                        <option value="3d">3 days of</option>
-                                        <option value="1w">1 week of</option>
-                                        <option value="2w">2 weeks of</option>
-                                        <option value="1m">1 month of</option>
-                                        <option value="2m">2 months of</option>
-                                        <option value="6m">6 months of</option>
-                                        <option value="1y">1 year of</option>
-                                    </select>
-                                    <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
-                                        id={Utils.GenerateGuid()}
-                                        ref={this.datePickerRef}
-                                        value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
-                                        onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
-                                        onPointerDown={this.openDatePicker}
-                                        placeholder="Value" />
-                                </div>
-                                <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
-                                    <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}> ADD KEY RESTRICTION </button>
-                                    <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}> APPLY FILTER </button>
-                                    <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.clearFilter}> CLEAR </button>
-                                </div>
-                            </div>
-                        </div>
                         <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
                                 <div className="commandEntry-drop">
-- 
cgit v1.2.3-70-g09d2


From d5c0ed9a29593583a4180973f854106dc10d4b30 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Apr 2020 02:32:47 -0400
Subject: moved filter to right side of collection.  got rid of back button in
 pivot view - click background.

---
 src/Utils.ts                                       | 10 +++--
 .../views/collections/CollectionTimeView.tsx       | 30 +++++++--------
 src/client/views/collections/CollectionView.scss   |  4 +-
 src/client/views/collections/CollectionView.tsx    | 43 +++++++++++++++-------
 .../views/collections/CollectionViewChromes.scss   |  8 +++-
 .../views/collections/CollectionViewChromes.tsx    | 12 +++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 8 files changed, 69 insertions(+), 43 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index a8cde0624..58f272ba5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -475,7 +475,9 @@ export function setupMoveUpEvents(
     e: React.PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
     upEvent: (e: PointerEvent) => void,
-    clickEvent: (e: PointerEvent) => void) {
+    clickEvent: (e: PointerEvent) => void,
+    stopPropagation: boolean = true
+) {
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
 
@@ -499,8 +501,10 @@ export function setupMoveUpEvents(
         document.removeEventListener("pointermove", _moveEvent);
         document.removeEventListener("pointerup", _upEvent);
     };
-    e.stopPropagation();
-    e.preventDefault();
+    if (stopPropagation) {
+        e.stopPropagation();
+        e.preventDefault();
+    }
     document.removeEventListener("pointermove", _moveEvent);
     document.removeEventListener("pointerup", _upEvent);
     document.addEventListener("pointermove", _moveEvent);
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 31b720b81..1760e2419 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -72,8 +72,22 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }), returnFalse, emptyFunction);
     }
 
+    contentsDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+            if (prevFilterIndex > 0) {
+                prevFilterIndex--;
+                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+                this.props.Document._prevFilterIndex = prevFilterIndex;
+            } else {
+                this.props.Document._docFilters = new List([]);
+            }
+        }), false);
+    }
+
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
             <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
@@ -132,20 +146,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
         return <div className={"pivotKeyEntry"}>
-            <button className="collectionTimeView-backBtn"
-                onClick={action(() => {
-                    let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-                    if (prevFilterIndex > 0) {
-                        prevFilterIndex--;
-                        this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                        this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                        this.props.Document._prevFilterIndex = prevFilterIndex;
-                    } else {
-                        this.props.Document._docFilters = new List([]);
-                    }
-                })}>
-                back
-            </button>
             <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
         </div>;
     }
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index a0b73b90f..d43dd387a 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -20,7 +20,7 @@
         top: 55%;
         border: 1px black solid;
         z-index: 2;
-        left: -10px;
+        right: -10px;
     }
     .collectionTimeView-treeView {
         display: flex;
@@ -28,7 +28,7 @@
         width: 200px;
         height: 100%;
         position: absolute;
-        left: 0;
+        right: 0;
         top: 0;
 
         .collectionTimeView-addfacet {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 831691f8c..0a3577bc7 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils, setupMoveUpEvents, returnFalse, returnZero } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -45,6 +45,7 @@ import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import CollectionMapView from './CollectionMapView';
+import { Transform } from 'prosemirror-transform';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -281,7 +282,6 @@ export class CollectionView extends Touchable<FieldViewProps> {
     set _facetWidth(value) { this.props.Document._facetWidth = value; }
 
     bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-this.facetWidth(), 0);
     facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth));
 
     @computed get dataDoc() {
@@ -389,7 +389,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     onPointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+            this._facetWidth = this.props.PanelWidth() - Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
             return false;
         }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
     }
@@ -415,27 +415,44 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}>
                     <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
                         <div className="collectionTimeView-button">
-                            <span className="collectionTimeView-span">Facet Filters</span>
                             <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                            <span className="collectionTimeView-span">Facet Filters</span>
                         </div>
                     </Flyout>
                 </div>
                 <div className="collectionTimeView-tree" key="tree">
-                    <CollectionTreeView {...this.props}
+                    <CollectionTreeView
+                        Document={facetCollection}
+                        DataDoc={facetCollection}
+                        fieldKey={`${this.props.fieldKey}-filter`}
                         CollectionView={this}
-                        treeViewHideTitle={true}
+                        ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                        ContainingCollectionView={this.props.ContainingCollectionView}
+                        PanelWidth={this.facetWidth}
+                        PanelHeight={this.props.PanelHeight}
                         NativeHeight={returnZero}
                         NativeWidth={returnZero}
+                        LibraryPath={emptyPath}
+                        rootSelected={this.props.rootSelected}
+                        renderDepth={1}
+                        dropAction={this.props.dropAction}
+                        ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                        addDocTab={returnFalse}
+                        pinToPres={returnFalse}
+                        isSelected={returnFalse}
+                        select={returnFalse}
+                        bringToFront={emptyFunction}
+                        active={this.props.active}
+                        whenActiveChanged={returnFalse}
+                        treeViewHideTitle={true}
+                        ContentScaling={returnOne}
+                        focus={returnFalse}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
                         ignoreFields={this.ignoreFields}
                         annotationsKey={""}
                         dontRegisterView={true}
-                        PanelWidth={this.facetWidth}
-                        DataDoc={facetCollection}
-                        Document={facetCollection}
                         backgroundColor={this.filterBackground}
-                        fieldKey={`${this.props.fieldKey}-filter`}
                         moveDocument={returnFalse}
                         removeDocument={returnFalse}
                         addDocument={returnFalse} />
@@ -461,7 +478,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }}
             onContextMenu={this.onContextMenu}>
             {this.showIsTagged()}
-            <div style={{ width: `calc(100% - ${this.facetWidth()}px)`, marginLeft: `${this.facetWidth()}px` }}>
+            <div style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
                 {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
             </div>
             {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
@@ -471,9 +488,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     :
                     ""))}
             {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
-                <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this.facetWidth()}px, 0px)` }} >
-                    <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
-                </div>
+                <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
             }
             {this.filterView}
         </div>);
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 9529ef525..5203eb55f 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -61,7 +61,8 @@
                 pointer-events: all;
                 // margin-top: 10px;
             }
-            .collectionViewBaseChrome-template {
+            .collectionViewBaseChrome-template,
+            .collectionViewBaseChrome-viewModes {
                 display: grid;
                 background: rgb(238, 238, 238);
                 color:grey;
@@ -69,9 +70,12 @@
                 margin-bottom:auto;
                 margin-left: 5px;
             }
+            .collectionViewBaseChrome-viewModes {
+                margin-left: 25px;
+            }
 
             .collectionViewBaseChrome-viewSpecs {
-                margin-left: 25px;
+                margin-left: 5px;
                 display: grid;
                 
                 .collectionViewBaseChrome-filterIcon {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index cef85e148..7315d2c4e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -276,12 +276,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             title="Collapse collection chrome" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
-                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
-                                <FontAwesomeIcon icon="filter" size="2x" />
-                            </div>
-                        </div>
-                        <div className="collectionViewBaseChrome-template" style={{ display: collapsed ? "none" : undefined }}>
+                        <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
                                 <div className="commandEntry-drop">
                                     <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
@@ -303,6 +298,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 </select>
                             </div>
                         </div>
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
+                        </div>
                         <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
                                 <div className="commandEntry-drop">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a77f09163..d98fe190c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -907,6 +907,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
         (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+        e.stopPropagation();
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
         const { x, y, z } = viewDef;
@@ -991,6 +992,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
+                    pointerEvents={this.props.layoutEngine?.() !== undefined ? "none" : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e7cb2c015..05ad98c43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -22,6 +22,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     width?: number;
     height?: number;
     jitterRotation: number;
+    pointerEvents?: "none";
     transition?: string;
     fitToBox?: boolean;
 }
@@ -92,7 +93,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 height: this.height,
                 zIndex: this.ZInd,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: this.props.Document.isBackground ? "none" : undefined
+                pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents
             }} >
 
             {!this.props.fitToBox ?
-- 
cgit v1.2.3-70-g09d2


From a6d17ef79fb94334735998d3d11f7ea068dea93a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Apr 2020 03:15:25 -0400
Subject: cleaned up some more of the pivot view event handling api

---
 .../views/collections/CollectionTimeView.tsx       | 23 ++++++++++------------
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 +++++--
 3 files changed, 16 insertions(+), 16 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 1760e2419..e05223ca0 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt, DocCastAsync } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
@@ -26,18 +26,15 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     _changing = false;
     @observable _layoutEngine = "pivot";
     @observable _collapsed: boolean = false;
-    componentWillUnmount() {
-        this.props.Document.onChildClick = undefined;
-    }
+    @observable _childClickedScript: Opt<ScriptField>;
+    @observable _viewDefDivClick: Opt<ScriptField>;
     async componentDidMount() {
-        const childText = "const alias = getAlias(this); switchView(alias, thisContainer.childDetailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
-        this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", thisContainer: Doc.name, shiftKey: "boolean" });
-        this.props.Document._fitToBox = true;
-        if (!this.props.Document.onViewDefClick) {
-            this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
-        }
-        this.props.Document.childDetailView = Cast(this.props.Document.childDetailView, Doc, null) ||// bcz: needs to be here to make sure the childDetailView layout template has been loaded when the first item is clicked;
-            DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const childText = "const alias = getAlias(this); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+        runInAction(() => {
+            this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
+            this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
+        });
     }
 
     layoutEngine = () => this._layoutEngine;
@@ -88,7 +85,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
-            <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0a3577bc7..19e235ff2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -278,7 +278,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
             onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
     }
-    get _facetWidth() { return NumCast(this.props.Document._facetWidth) }
+    get _facetWidth() { return NumCast(this.props.Document._facetWidth); }
     set _facetWidth(value) { this.props.Document._facetWidth = value; }
 
     bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d98fe190c..f19181a20 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -44,6 +44,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CollectionViewType } from "../CollectionView";
+import { Script } from "vm";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -69,6 +70,8 @@ type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchem
 const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
 export type collectionFreeformViewProps = {
     forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
+    childClickScript?: ScriptField;
+    viewDefDivClick?: ScriptField;
 };
 
 @observer
@@ -847,7 +850,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     }
 
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
-    @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+    @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
 
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
@@ -906,7 +909,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     }
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
-        (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+        (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
         e.stopPropagation();
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
-- 
cgit v1.2.3-70-g09d2


From 67c56296339bd1c760b704c1d14710ec6f575d49 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 Apr 2020 22:55:10 -0400
Subject: added freeze button for web boxes.  cleaned up pointerevent setting
 throughout code.

---
 src/client/views/InkingStroke.scss                 |  8 +++-
 src/client/views/InkingStroke.tsx                  |  5 +--
 src/client/views/Main.scss                         |  1 -
 src/client/views/MainView.scss                     |  1 +
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/MarqueeView.scss            |  1 -
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 src/client/views/nodes/FormattedTextBox.scss       |  1 -
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/client/views/nodes/ImageBox.scss               |  1 -
 src/client/views/nodes/KeyValueBox.scss            |  1 -
 src/client/views/nodes/LabelBox.scss               |  1 -
 src/client/views/nodes/LinkAnchorBox.scss          |  1 -
 src/client/views/nodes/PresBox.scss                |  1 -
 src/client/views/nodes/QueryBox.scss               |  1 -
 src/client/views/nodes/ScreenshotBox.scss          |  5 ---
 src/client/views/nodes/ScriptingBox.scss           |  1 -
 src/client/views/nodes/SliderBox.scss              |  1 -
 src/client/views/nodes/VideoBox.scss               |  7 ++--
 src/client/views/nodes/WebBox.scss                 | 15 ++++----
 src/client/views/nodes/WebBox.tsx                  | 44 +++++++++-------------
 22 files changed, 42 insertions(+), 64 deletions(-)

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

diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss
index cdbfdcff3..433433a42 100644
--- a/src/client/views/InkingStroke.scss
+++ b/src/client/views/InkingStroke.scss
@@ -1,3 +1,7 @@
-.inkingStroke-marker {
-    mix-blend-mode: multiply
+.inkingStroke {
+    mix-blend-mode: multiply;
+    stroke-linejoin: round;
+    stroke-linecap: round;
+    overflow: visible !important;
+    transform-origin: top left;
 }
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f66c04e1f..7a318d5c2 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,4 +1,3 @@
-import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { documentSchema } from "../../new_fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../new_fields/InkField";
@@ -47,14 +46,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const scaleX = this.props.PanelWidth() / width;
         const scaleY = this.props.PanelHeight() / height;
         return (
-            <svg
+            <svg className="inkingStroke"
                 width={width}
                 height={height}
                 style={{
-                    transformOrigin: "top left",
                     transform: `scale(${scaleX}, ${scaleY})`,
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
-                    pointerEvents: "all"
                 }}
                 onContextMenu={() => {
                     ContextMenu.Instance.addItem({
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4709e7ef2..a2a9ceca5 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -24,7 +24,6 @@ body {
 .jsx-parser {
     width: 100%;
     height: 100%;
-    pointer-events: none;
     border-radius: inherit;
     position: inherit;
    // background: inherit;
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index e95802e54..e9f2248ad 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -28,6 +28,7 @@
     width: 100%;
     height: 100%;
     position: absolute;
+    pointer-events: all;
     top: 0;
     left: 0;
     z-index: 1;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d2c8cc3ad..510c9924b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -453,7 +453,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
-                    pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
+                    pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? undefined : "none"
                 }} >
                 {Doc.GetT(this.props.document, "editTitle", "boolean", true) ?
                     this.editableView("title") :
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 19e235ff2..877e4355f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -472,7 +472,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         };
         return (<div className={"collectionView"}
             style={{
-                pointerEvents: this.props.Document.isBackground ? "none" : "all",
+                pointerEvents: this.props.Document.isBackground ? "none" : undefined,
                 boxShadow: this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
                     `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31)" : "#9c9396"} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
             }}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 18d6da0da..1291e7dc1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -6,7 +6,6 @@
     width:100%;
     height:100%;
     overflow: hidden;
-    pointer-events: inherit;
     border-radius: inherit;
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fb0b1d4f4..2f22488e4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1092,7 +1092,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const titleView = (!showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
                 position: showTextTitle ? "relative" : "absolute",
-                pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : "all",
+                pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
                     contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
@@ -1154,7 +1154,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 transformOrigin: this._animate ? "center center" : undefined,
                 transform: this._animate ? `scale(${this._animate})` : undefined,
                 transition: !this._animate ? StrCast(this.Document.transition) : this._animate < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
-                pointerEvents: this.ignorePointerEvents ? "none" : "all",
+                pointerEvents: this.ignorePointerEvents ? "none" : undefined,
                 color: StrCast(this.layoutDoc.color, "inherit"),
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 7d40b3149..7a05ec3a3 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -25,7 +25,6 @@
     overflow-x: hidden;
     color: initial;
     height: 100%;
-    pointer-events: all;
     max-height: 100%;
     display: flex;
     flex-direction: row;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d641dc791..62911dc5c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1185,7 +1185,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"]),
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.hideOnLeave ? "white" : "inherit",
-                    pointerEvents: interactive ? "none" : "all",
+                    pointerEvents: interactive ? "none" : undefined,
                     fontSize: NumCast(this.layoutDoc.fontSize, 13),
                     fontFamily: StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
                 }}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 7bbf4a368..49425c2c2 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,6 +1,5 @@
 .imageBox,
 .imageBox-dragging {
-    pointer-events: all;
     border-radius: inherit;
     width: 100%;
     height: 100%;
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index a26880c9e..eb7c2f32b 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -8,7 +8,6 @@
     border-radius: $border-radius;
     box-sizing: border-box;
     display: inline-block;
-    pointer-events: all;
     cursor: default;
     .imageBox-cont img {
         width: auto;
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index ab5b2c6b3..56dd86ff9 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -1,7 +1,6 @@
 .labelBox-outerDiv {
     width: 100%;
     height: 100%;
-    pointer-events: all;
     border-radius: inherit;
     display: flex;
     flex-direction: column;
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 7b6093ebd..24f9c1ea0 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -4,7 +4,6 @@
     width: 15;
     height: 15;
     border-radius: 20px;
-    pointer-events: all;
     user-select: none;
 
     .linkAnchorBox-linkCloser {
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index ba8389fda..6676d8cd2 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -10,7 +10,6 @@
     letter-spacing: 2px;
     overflow: hidden;
     transition: 0.7s opacity ease;
-    pointer-events: all;
 
     .presBox-buttons {
         padding: 10px;
diff --git a/src/client/views/nodes/QueryBox.scss b/src/client/views/nodes/QueryBox.scss
index 82f64054c..b5f90aa1e 100644
--- a/src/client/views/nodes/QueryBox.scss
+++ b/src/client/views/nodes/QueryBox.scss
@@ -2,5 +2,4 @@
     width: 100%;
     height: 100%; 
     position: absolute;
-    pointer-events: all;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
index 6cc184948..141960f60 100644
--- a/src/client/views/nodes/ScreenshotBox.scss
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -1,5 +1,4 @@
 .screenshotBox {
-    pointer-events: all;
     transform-origin: top left;
     background: white;
     color: black;
@@ -21,10 +20,6 @@
     height: Auto; 
 }
 
-.screenshotBox-content-interactive, .screenshotBox-content-fullScreen  {
-    pointer-events: all;
-}
-
 .screenshotBox-uiButtons {
     background:dimgray;
     border: orange solid 1px;
diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss
index 678a1a22d..43695f00d 100644
--- a/src/client/views/nodes/ScriptingBox.scss
+++ b/src/client/views/nodes/ScriptingBox.scss
@@ -3,7 +3,6 @@
     height: 100%;
     display: flex;
     flex-direction: column;
-    pointer-events: all;
     background-color: rgb(241, 239, 235);
     padding: 10px;
     .scriptingBox-inputDiv {
diff --git a/src/client/views/nodes/SliderBox.scss b/src/client/views/nodes/SliderBox.scss
index 4ef277d8c..78015bd70 100644
--- a/src/client/views/nodes/SliderBox.scss
+++ b/src/client/views/nodes/SliderBox.scss
@@ -1,7 +1,6 @@
 .sliderBox-outerDiv {
     width: 100%;
     height: 100%;
-    pointer-events: all;
     border-radius: inherit;
     display: flex;
     flex-direction: column;
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index fabbf5196..0c0854ac2 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,5 +1,4 @@
 .videoBox {
-    pointer-events: all;
     transform-origin: top left;
     .videoBox-viewer {
         opacity: 0.99;  // hack!  overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
@@ -24,9 +23,9 @@
     height: 100%;
 }
 
-.videoBox-content-interactive, .videoBox-content-fullScreen, .videoBox-content-YouTube-fullScreen  {
-    pointer-events: all;
-}
+// .videoBox-content-interactive, .videoBox-content-fullScreen, .videoBox-content-YouTube-fullScreen  {
+//     pointer-events: all;
+// }
 
 .videoBox-time{
     color : white;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 3014b9425..cdeac4bd1 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -18,8 +18,7 @@
         display:none;
     }
 }
-.webBox-cont,
-.webBox-cont-dragging {
+.webBox-cont {
     padding: 0vw;
     position: absolute;
     top: 0;
@@ -32,8 +31,6 @@
 }
 
 .webBox-cont-interactive {
-    pointer-events: all;
-
     span {
         user-select: text !important;
     }
@@ -49,7 +46,6 @@
     width: 100%;
     height: 100%;
     position: absolute;
-    pointer-events: all;
 }
 
 .webBox-button {
@@ -59,7 +55,7 @@
     height: 100%;
 }
 
-.webView-urlEditor {
+.webBox-urlEditor {
     position: relative;
     opacity: 0.9;
     z-index: 9001;
@@ -74,6 +70,12 @@
         padding-bottom: 10px;
         overflow: hidden;
 
+        .webBox-freeze {
+            display: flex;
+            align-items: center; 
+            justify-content: center;
+        }
+
         .editorBase {
             display: flex;
 
@@ -115,7 +117,6 @@
     width: 100%;
     height: 100%;
     position: absolute;
-    pointer-events: all;
 
     .indicator {
         position: absolute;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b48437464..26e947f5b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { faStickyNote, faLock, faUnlock } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, FieldResult } from "../../../new_fields/Doc";
@@ -23,6 +23,7 @@ import React = require("react");
 import * as WebRequest from 'web-request';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { DocumentView } from "./DocumentView";
 const htmlToText = require("html-to-text");
 
 library.add(faStickyNote);
@@ -116,31 +117,21 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         }
     }
 
-
-    switchToText = () => {
-        let url: string = "";
-        const field = Cast(this.rootDoc[this.props.fieldKey], WebField);
-        if (field) url = field.url.href;
-
-        const newBox = Docs.Create.TextDocument(url, {
-            x: NumCast(this.rootDoc.x),
-            y: NumCast(this.rootDoc.y),
-            title: url,
-            _width: 200,
-            _height: 70,
-        });
-
-        SelectionManager.SelectedDocuments().map(dv => {
-            dv.props.addDocument?.(newBox);
-            dv.props.removeDocument?.(dv.rootDoc);
-        });
-
-        Doc.BrushDoc(newBox);
+    toggleNativeDimensions = () => {
+        if (this.Document._nativeWidth || this.Document._nativeHeight) {
+            DocumentView.unfreezeNativeDimensions(this.layoutDoc);
+            this.layoutDoc.lockedTransform = false;
+        }
+        else {
+            Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
+            this.layoutDoc.lockedTransform = true;
+        }
     }
 
     urlEditor() {
+        const frozen = this.layoutDoc._nativeWidth && this.layoutDoc._nativeHeight;
         return (
-            <div className="webView-urlEditor" style={{ top: this.collapsed ? -70 : 0 }}>
+            <div className="webBox-urlEditor" style={{ top: this.collapsed ? -70 : 0 }}>
                 <div className="urlEditor">
                     <div className="editorBase">
                         <button className="editor-collapse"
@@ -169,8 +160,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                                 <button className="submitUrl" onClick={this.submitURL}>
                                     SUBMIT
                                 </button>
-                                <div className="switchToText" title="Convert web to text doc" onClick={this.switchToText} style={{ display: "flex", alignItems: "center", justifyContent: "center" }} >
-                                    <FontAwesomeIcon icon={faStickyNote} size={"lg"} />
+                                <div className="webBox-freeze" title={frozen ? "Unfreeze" : "Freeze Dimensions"} onClick={this.toggleNativeDimensions} >
+                                    <FontAwesomeIcon icon={frozen ? faUnlock : faLock} size={"lg"} />
                                 </div>
                             </div>
                         </div>
@@ -331,7 +322,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     {content}
                 </div>
                 {!frozen ? (null) :
-                    <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
+                    <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }}
+                        onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
                         <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
                             <div className="indicator" ref={this._iframeIndicatorRef}></div>
                             <div className="dragger" ref={this._iframeDragRef}></div>
@@ -350,7 +342,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             }} >
             {this.content}
             <div className={"webBox-outerContent"} ref={this._outerRef}
-                style={{ pointerEvents: this.layoutDoc._nativeHeight ? "all" : "none" }}
+                style={{ pointerEvents: this.layoutDoc._nativeHeight && !this.layoutDoc.isBackground ? "all" : "none" }}
                 onWheel={e => e.stopPropagation()}
                 onScroll={e => {
                     if (this._iframeRef.current!.contentDocument!.children[0].scrollTop !== this._outerRef.current!.scrollTop) {
-- 
cgit v1.2.3-70-g09d2


From 0bc20bc8c1f2b6b77624d37047d63a31fa0cf299 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Apr 2020 11:25:30 -0400
Subject: added additional rendition menu item

---
 src/client/views/collections/CollectionView.tsx | 67 ++++++++++++++-----------
 1 file changed, 37 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 877e4355f..178c3189b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -192,38 +192,45 @@ export class CollectionView extends Touchable<FieldViewProps> {
     }
 
 
+    setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
+        const existingVm = ContextMenu.Instance.findByDescription(category);
+        const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
+
+        subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
+        if (addExtras && CollectionView._safeMode) {
+            ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
+        }
+        subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
+        subItems.push({ description: "Treeview", event: () => func(CollectionViewType.Tree), icon: "tree" });
+        subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" });
+        subItems.push({ description: "Stacking (AutoHeight)", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" });
+        subItems.push({ description: "Staff", event: () => func(CollectionViewType.Staff), icon: "music" });
+        subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" });
+        subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" });
+        subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" });
+        subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
+        subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
+        subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
+        if (addExtras) switch (this.props.Document._viewType) {
+            case CollectionViewType.Freeform: {
+                subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
+                break;
+            }
+        }
+        addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
+        !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
+    }
+
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            const existingVm = ContextMenu.Instance.findByDescription("View Modes...");
-            const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
-            subItems.push({ description: "Freeform", event: () => { this.props.Document._viewType = CollectionViewType.Freeform; }, icon: "signature" });
-            if (CollectionView._safeMode) {
-                ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document._viewType = CollectionViewType.Invalid, icon: "project-diagram" });
-            }
-            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 (AutoHeight)", event: () => {
-                    this.props.Document._viewType = CollectionViewType.Stacking;
-                    this.props.Document._autoHeight = true;
-                }, icon: "ellipsis-v"
-            });
-            subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" });
-            subItems.push({ description: "Multicolumn", event: () => this.props.Document._viewType = CollectionViewType.Multicolumn, icon: "columns" });
-            subItems.push({ description: "Multirow", event: () => this.props.Document._viewType = CollectionViewType.Multirow, icon: "columns" });
-            subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
-            subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" });
-            subItems.push({ description: "Pivot/Time", event: () => this.props.Document._viewType = CollectionViewType.Time, icon: "columns" });
-            subItems.push({ description: "Map", event: () => this.props.Document._viewType = CollectionViewType.Map, icon: "globe-americas" });
-            switch (this.props.Document._viewType) {
-                case CollectionViewType.Freeform: {
-                    subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
-                    break;
-                }
-            }
-            subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
-            !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
+
+            this.setupViewTypes("View Modes ...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
+            this.setupViewTypes("Additional Rendition ...", vtype => {
+                const newRendition = Doc.MakeAlias(this.props.Document);
+                newRendition._viewType = vtype;
+                this.props.addDocTab(newRendition, "onRight");
+                return newRendition;
+            }, false);
 
             const existing = ContextMenu.Instance.findByDescription("Layout...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
-- 
cgit v1.2.3-70-g09d2


From 3d629495644631e6b614b1b48f1d32d6c7aeefc6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Apr 2020 17:25:17 -0400
Subject: added parameterization capability to layout strings.  added Div's
 that can take Dash field values

---
 src/client/util/RichTextSchema.tsx              |  6 ++--
 src/client/views/DocumentDecorations.tsx        |  4 +--
 src/client/views/collections/CollectionView.tsx |  7 ++--
 src/client/views/nodes/DocumentContentsView.tsx | 45 +++++++++++++++++++++----
 4 files changed, 46 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1522f5e21..d23962d5c 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -852,9 +852,9 @@ export class DashDocView {
             };
             this._renderDisposer?.();
             this._renderDisposer = reaction(() => {
-                if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                    finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
-                }
+                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
+                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
+                // }
                 const layoutKey = StrCast(finalLayout.layoutKey);
                 const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
                 if (finalLayout !== dashDoc && finalKey) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c8766a6b3..bd41788d4 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,7 +3,7 @@ import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faT
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DataSym } from "../../new_fields/Doc";
+import { Doc, DataSym, Field } from "../../new_fields/Doc";
 import { PositionDocument } from '../../new_fields/documentSchemas';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { Cast, StrCast, NumCast } from "../../new_fields/Types";
@@ -366,7 +366,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
             }
             if (this._titleControlString.startsWith("#")) {
-                return selected.props.Document[this._titleControlString.substring(1)]?.toString() || "-unset-";
+                return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
             }
             return this._accumulatedTitle;
         } else if (SelectionManager.SelectedDocuments().length > 1) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 178c3189b..4448fcf21 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -211,11 +211,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
         subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
         subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
         subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
-        if (addExtras) switch (this.props.Document._viewType) {
-            case CollectionViewType.Freeform: {
-                subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
-                break;
-            }
+        if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
+            subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
         !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 7522af3a3..765416658 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,8 +1,8 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../new_fields/Doc";
-import { Cast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Without } from "../../../Utils";
+import { Doc, Opt, Field } from "../../../new_fields/Doc";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { OmitKeys, Without, emptyPath } from "../../../Utils";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -53,6 +53,31 @@ class ObserverJsxParser1 extends JsxParser {
 
 const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 
+
+interface DivProps {
+    Document: Doc;
+}
+
+@observer
+export class Div extends React.Component<DivProps> {
+    render() {
+        const style: { [key: string]: any } = {};
+        const divKeys = OmitKeys(this.props, ["children", "Document", "key", "__proto__"]).omit;
+        Object.keys(divKeys).map((prop: string) => {
+            let p = (this.props as any)[prop] as string;
+            if (p?.startsWith("@")) {  // bcz: extend this to support expression -- is this really a script?
+                const key = p.substring(1);
+                const n = Cast(this.props.Document[key], "number", null);
+                p = n ? n.toString() : StrCast(this.props.Document[key], p);
+            }
+            style[prop] = p;
+        });
+        return <div style={style}>
+            {this.props.children}
+        </div>;
+    }
+}
+
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: (outsideReaction: boolean) => boolean,
@@ -102,21 +127,29 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         TraceMobx();
-        return (this.props.renderDepth > 12 || !this.layout || !this.layoutDoc) ? (null) :
+        let layoutFrame = this.layout;
+        const replacer = (match: any, p1: string, offset: any, string: any) => {
+            const n = Cast(this.props.Document[p1], "number", null);
+            return n !== undefined ? n.toString() : StrCast(this.props.Document[p1], p1);
+        };
+        layoutFrame = layoutFrame.replace(/\{([a-zA-Z0-9_-]+)\}/g, replacer);
+        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
             this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
                 <FormattedTextBox {...this.CreateBindings().props} fieldKey={this.props.forceFieldKey} />
                 :
                 <ObserverJsxParser
+                    key={42}
                     blacklistedAttrs={[]}
+                    renderInWrapper={false}
                     components={{
                         FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
                         CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                         PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
                         ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
-                        RecommendationsBox, ScreenshotBox
+                        RecommendationsBox, ScreenshotBox, Div
                     }}
                     bindings={this.CreateBindings()}
-                    jsx={this.layout}
+                    jsx={layoutFrame}
                     showWarnings={true}
 
                     onError={(test: any) => { console.log(test); }}
-- 
cgit v1.2.3-70-g09d2


From 04bc902ad31eaf07e3a5edc34854744a88ffb3bc Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 22 Apr 2020 14:08:19 -0400
Subject: fixed clicking gear icon on doc decorations.  moved repl to Drag
 items.  Removed Open... menu.  Added Open FIelds & Full SCreen to Open New
 Persepectives.

---
 src/client/views/DocumentDecorations.tsx               |  4 ++--
 src/client/views/MainView.tsx                          |  3 ++-
 src/client/views/OverlayView.tsx                       |  6 +++++-
 src/client/views/collections/CollectionView.tsx        |  8 ++------
 src/client/views/nodes/DocumentView.tsx                | 11 ++---------
 src/server/authentication/models/current_user_utils.ts |  9 +++++----
 6 files changed, 18 insertions(+), 23 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bd41788d4..5d6242201 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -144,8 +144,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action onSettingsClick = (e: PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey) {
             let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
-            while (child.children.length && child.className !== "jsx-parser") child = child.children[0];
-            simulateMouseClick(child.children[0], e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+            while (child.children.length) child = child.children[0];
+            simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
         }
     }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f8e246315..8fb67c435 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTerminal, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
+import { faTerminal, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -103,6 +103,7 @@ export class MainView extends React.Component {
         }
 
         library.add(faTerminal);
+        library.add(faCalculator);
         library.add(faWindowMaximize);
         library.add(faFileAlt);
         library.add(faAddressCard);
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 614217bc8..20aa14f84 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -9,6 +9,8 @@ import { Transform } from "../util/Transform";
 import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
 import { DocumentView } from "./nodes/DocumentView";
 import './OverlayView.scss';
+import { Scripting } from "../util/Scripting";
+import { ScriptingRepl } from './ScriptingRepl';
 
 export type OverlayDisposer = () => void;
 
@@ -210,4 +212,6 @@ export class OverlayView extends React.Component {
             </div>
         );
     }
-}
\ No newline at end of file
+}
+// bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error
+Scripting.addGlobal(function addOverlayWindow(Tag: string, options: OverlayElementOptions) { const x = <ScriptingRepl />; OverlayView.Instance.addWindow(x, options); });
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4448fcf21..ade54f2c9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -221,8 +221,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
 
-            this.setupViewTypes("View Modes ...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
-            this.setupViewTypes("Additional Rendition ...", vtype => {
+            this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
+            this.setupViewTypes("Open New Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
                 this.props.addDocTab(newRendition, "onRight");
@@ -242,10 +242,6 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
 
-            const open = ContextMenu.Instance.findByDescription("Open...");
-            const openItems = open && "subitems" in open ? open.subitems : [];
-            !open && ContextMenu.Instance.addItem({ description: "Open...", subitems: openItems, icon: "hand-point-right" });
-
             const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
             const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 286044ab2..d8783c458 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,8 +40,6 @@ import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
 import { InkingControl } from '../InkingControl';
 import { KeyphraseQueryView } from '../KeyphraseQueryView';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { RadialMenu } from './RadialMenu';
@@ -737,17 +735,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
 
-        const open = ContextMenu.Instance.findByDescription("Open...");
+        const open = cm.findByDescription("Open New Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
         openItems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" });
-        openItems.push({ description: "Open Tab        ", event: () => this.props.addDocTab(this.props.Document, "inTab", this.props.LibraryPath), icon: "folder" });
-        openItems.push({ description: "Open Right      ", event: () => this.props.addDocTab(this.props.Document, "onRight", this.props.LibraryPath), icon: "caret-square-right" });
-        openItems.push({ description: "Open Alias Tab  ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "inTab"), icon: "folder" });
-        openItems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" });
         openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        openItems.push({ description: "Open Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
-        !open && cm.addItem({ description: "Open...", subitems: openItems, icon: "external-link-alt" });
+        !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
 
 
         const existingOnClick = cm.findByDescription("OnClick...");
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 3955f64fc..b4f9ead53 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -84,7 +84,7 @@ export class CurrentUserUtils {
 
         if (doc["template-buttons"] === undefined) {
             doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, doc["template-button-query"] as Doc], {
-                title: "Template Item Creators", _xMargin: 0, _showTitle: "title",
+                title: "Compound Item Creators", _xMargin: 0, _showTitle: "title",
                 _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
             }));
@@ -186,6 +186,7 @@ export class CurrentUserUtils {
             { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
             { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
             { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
+            { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
             { title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' },
             { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
             { title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
@@ -194,10 +195,10 @@ export class CurrentUserUtils {
             // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
             // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc },
             // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc },
-            { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
             { title: "Drag a document previewer", label: "Prev", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
-            // { title: "buxton", icon: "cloud-upload-alt", ignoreClick: true, drag: "Docs.Create.Buxton()" },
+            { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
         ];
+
     }
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
@@ -229,7 +230,7 @@ export class CurrentUserUtils {
 
         if (dragCreatorSet === undefined) {
             doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
-                title: "Standard Item Creators", _showTitle: "title", _xMargin: 0,
+                title: "Basic Item Creators", _showTitle: "title", _xMargin: 0,
                 _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
             }));
-- 
cgit v1.2.3-70-g09d2


From 72f614f023a4bac6a2f0b63a1b1a16e6f4545c7a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 00:53:08 -0400
Subject: fixed browser back after link navigation. simplified menus more.
 fixed jitter mode to be seeded. fixed menu gear for  collections. .

---
 src/client/util/History.ts                         |  6 +++-
 src/client/util/ProsemirrorExampleTransfer.ts      |  4 +--
 src/client/views/DocumentDecorations.tsx           |  6 +++-
 .../views/collections/CollectionDockingView.tsx    |  6 +++-
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++++------
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 +++--
 src/client/views/nodes/DocumentBox.tsx             |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 41 ++++++++++++++++++++--
 src/client/views/nodes/KeyValueBox.tsx             | 23 ++++++++----
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  2 +-
 src/client/views/nodes/SliderBox.tsx               |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  2 +-
 20 files changed, 103 insertions(+), 39 deletions(-)

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

diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 545e8acb4..2c53d7e52 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -40,8 +40,12 @@ export namespace HistoryUtil {
         // }
     }
 
+    let _lastStatePush = 0;
     export function pushState(state: ParsedUrl) {
-        history.pushState(state, "", createUrl(state));
+        if (Date.now() - _lastStatePush > 1000) {
+            history.pushState(state, "", createUrl(state));
+        }
+        _lastStatePush = Date.now();
     }
 
     export function replaceState(state: ParsedUrl) {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 42247f177..680f48f70 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -154,7 +154,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (originalDoc instanceof Doc) {
             const newDoc = Docs.Create.TextDocument("", {
-                title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -172,7 +172,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
             const newDoc = Docs.Create.TextDocument("", {
-                title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 43c7751fa..6e699bc68 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -134,7 +134,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action onSettingsClick = (e: PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey) {
             let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
-            while (child.children.length) child = child.children[0];
+            while (child.children.length) {
+                const next = Array.from(child.children).find(c => !c.className.includes("collectionViewChrome"));
+                if (next) child = next;
+                else break;
+            }
             simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5e77bc0bb..0d859c3f1 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,6 +29,7 @@ import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { DockingViewButtonSelector } from './ParentDocumentSelector';
 import React = require("react");
+import { CollectionViewType } from './CollectionView';
 library.add(faFile);
 const _global = (window /* browser */ || global /* node */) as any;
 
@@ -93,6 +94,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     @action
     public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) {
+        if (docView.props.Document._viewType === CollectionViewType.Docking && docView.props.Document.layoutKey === "layout") {
+            return MainView.Instance.openWorkspace(docView.props.Document);
+        }
         const document = Doc.MakeAlias(docView.props.Document);
         const newItemStackConfig = {
             type: 'stack',
@@ -771,7 +775,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
-        if (doc.dockingConfig) {
+        if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
             return MainView.Instance.openWorkspace(doc);
         } else if (location === "onRight") {
             return CollectionDockingView.AddRightSplit(doc, libraryPath);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b15649d83..24a3119cc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -397,7 +397,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         if (!e.isPropagationStopped()) {
             const subItems: ContextMenuProps[] = [];
             subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
-            ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
         }
     }
 
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 742a818bc..06ebf6d2d 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -102,7 +102,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined; }, icon: "compress-arrows-alt" });
         layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
     }
     @computed get _allFacets() {
         const facets = new Set<string>();
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f589c2c76..a052d045c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -711,7 +711,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
-            ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
         }
         ContextMenu.Instance.addItem({
             description: "Buxton Layout", icon: "eye", event: () => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ade54f2c9..4f1242c33 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -229,7 +229,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance.findByDescription("Layout...");
+            const existing = ContextMenu.Instance.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -240,7 +240,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
             const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3b5101a4d..55e55e56c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1048,16 +1048,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.children && this.props.annotationsKey) return;
-        const layoutItems: ContextMenuProps[] = [];
-
-        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.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
-        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => 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: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+        const options = ContextMenu.Instance.findByDescription("Options...");
+        const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+
+        optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+        optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+        optionItems.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({
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
+        optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
                 input.type = "file";
@@ -1085,7 +1086,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         });
 
-        ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
     }
 
     private childViews = () => {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8f40ea2be..6ff6d1b42 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -162,7 +162,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     stopRecording = action(() => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3a7e005ac..615b05e43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,3 @@
-import anime from "animejs";
 import { computed, IReactionDisposer, observable, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
@@ -29,8 +28,14 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
     @observable _animPos: number[] | undefined = undefined;
+    random(min: number, max: number) { // min should not be equal to max
+        const mseed = Math.abs(this.X * this.Y);
+        const seed = (mseed * 9301 + 49297) % 233280;
+        var rnd = seed / 233280;
+        return min + rnd * (max - min);
+    }
     get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
-    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; }
+    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
     get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index ac562f19a..7583aa070 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -45,7 +45,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         funcs.push({ description: (this.props.Document.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => Doc.GetProto(this.props.Document).excludeCollections = !this.props.Document.excludeCollections, icon: "expand-arrows-alt" });
         funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
 
-        ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
     @computed get contentDoc() {
         return (this.props.Document.isTemplateDoc || this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a19ba6506..cab7b965a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -741,7 +741,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const existingOnClick = cm.findByDescription("OnClick...");
         const 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: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
+        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2c89d53d8..1329f7dcf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -48,6 +48,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format
 import React = require("react");
 import { PrefetchProxy } from '../../../new_fields/Proxy';
 import { makeTemplate } from '../../util/DropConverter';
+import { DocumentView } from './DocumentView';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -396,6 +397,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
     }
     specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+
         const funcs: ContextMenuProps[] = [];
         this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
         funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
@@ -407,10 +410,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Audio", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
-            funcs.push({
+            highlighting.push({
                 description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
                     e.stopPropagation();
                     if (FormattedTextBox._highlights.indexOf(option) === -1) {
@@ -421,8 +426,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.updateHighlights();
                 }, icon: "expand-arrows-alt"
             }));
+        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
 
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+        const change = cm.findByDescription("Change Perspective...");
+        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
+
+        const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
+        const noteTypes = DocListCast(noteTypesDoc?.data);
+        noteTypes.forEach(note => {
+            changeItems.push({
+                description: StrCast(note.title), event: () => {
+                    Doc.setNativeView(this.props.Document);
+                    DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }, icon: "eye"
+            })
+        });
+        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" })
+        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+
+        const open = cm.findByDescription("Open New Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+
+        openItems.push({
+            description: "FreeForm", event: () => {
+                const alias = Doc.MakeAlias(this.rootDoc);
+                DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+                this.props.addDocTab(alias, "onRight");
+            }, icon: "eye"
+        })
+        !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+
     }
 
     recordDictation = () => {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 7aad6f90e..2970674a2 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -16,6 +16,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
 import React = require("react");
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 
 export type KVPScript = {
     script: CompiledScript;
@@ -34,11 +36,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
-
-    constructor(props: FieldViewProps) {
-        super(props);
-    }
+    get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
@@ -234,13 +232,26 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         return new Doc;
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+        const open = cm.findByDescription("Change Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+        openItems.push({
+            description: "Default Perspective", event: () => {
+                this.props.addDocTab(this.fieldDocToLayout, "inTab");
+                this.props.addDocTab(this.props.Document, "close");
+            }, icon: "image"
+        });
+        !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
+    }
+
     render() {
         const dividerDragger = this.splitPercentage === 0 ? (null) :
             <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
                 <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
             </div>;
 
-        return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}>
+        return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
             <table className="keyValueBox-table">
                 <tbody className="keyValueBox-tbody">
                     <tr className="keyValueBox-header">
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8e3c15908..eb647d0e4 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -109,7 +109,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
         funcs.push({ description: "Toggle Always Show Link", event: () => this.props.Document.linkDisplay = !this.props.Document.linkDisplay, icon: "eye" });
 
-        ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6db36e43c..3712c648e 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -210,7 +210,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         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" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     @computed get contentScaling() { return this.props.ContentScaling(); }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 11b24b059..125690dc7 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -120,7 +120,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
                     this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                 }), icon: "expand-arrows-alt"
             });
-            ContextMenu.Instance.addItem({ description: "Screenshot Funcs...", subitems: subitems, icon: "video" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
     }
 
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 746ea0b64..b2d451ea8 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -40,7 +40,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) });
-        ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
     onChange = (values: readonly number[]) => runInAction(() => {
         this.dataDoc[this.minThumbKey] = values[0];
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 588068334..613929bca 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -209,7 +209,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                     this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                 }), icon: "expand-arrows-alt"
             });
-            ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 81229ca27c6e21d4b5f0ee3a5168c766dcfb954a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 13:28:05 -0400
Subject: fixed presbox navigation bar layout height.

---
 src/client/views/collections/CollectionView.tsx | 4 ++--
 src/client/views/nodes/DocumentView.tsx         | 4 ++--
 src/client/views/nodes/FormattedTextBox.tsx     | 4 ++--
 src/client/views/nodes/PresBox.scss             | 5 +++++
 4 files changed, 11 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4f1242c33..47e79f088 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -201,7 +201,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
         }
         subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
-        subItems.push({ description: "Treeview", event: () => func(CollectionViewType.Tree), icon: "tree" });
+        subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
         subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" });
         subItems.push({ description: "Stacking (AutoHeight)", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" });
         subItems.push({ description: "Staff", event: () => func(CollectionViewType.Staff), icon: "music" });
@@ -222,7 +222,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
 
             this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
-            this.setupViewTypes("Open New Perspective...", vtype => {
+            this.setupViewTypes("New Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
                 this.props.addDocTab(newRendition, "onRight");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc8773ed5..4bbdde3ea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -687,11 +687,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
         !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "compass" });
 
-        const open = cm.findByDescription("Open New Perspective...");
+        const open = cm.findByDescription("New Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
         openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+        !open && cm.addItem({ description: "New Perspective...", subitems: openItems, icon: "external-link-alt" });
 
 
         const existingOnClick = cm.findByDescription("OnClick...");
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f71343176..a018f17e8 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -444,7 +444,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
 
-        const open = cm.findByDescription("Open New Perspective...");
+        const open = cm.findByDescription("New Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
 
         openItems.push({
@@ -454,7 +454,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 this.props.addDocTab(alias, "onRight");
             }), icon: "eye"
         });
-        !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+        !open && cm.addItem({ description: "New Perspective...", subitems: openItems, icon: "external-link-alt" });
 
     }
 
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 6676d8cd2..78c19f351 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -11,6 +11,11 @@
     overflow: hidden;
     transition: 0.7s opacity ease;
 
+    .presBox-listCont {
+        position: absolute;
+        height: calc(100% - 25px);
+        width: 100%;
+    }
     .presBox-buttons {
         padding: 10px;
         width: 100%;
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 308ae1521cbd58126d35239cfdd5a138f6fa1fe0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 15:40:07 -0400
Subject: got rid of focus mechanism on click.  fixed shift-click to
 multiselect.  fixed pile view interactions.  added FollowLink on click menu
 items.

---
 src/client/util/DragManager.ts                     |  9 +++-
 src/client/util/ProsemirrorExampleTransfer.ts      | 18 +++++--
 src/client/views/ContextMenu.tsx                   |  9 ++++
 src/client/views/TemplateMenu.tsx                  |  5 +-
 .../views/collections/CollectionPileView.tsx       | 38 +++++++++++--
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 20 +++----
 src/client/views/nodes/DocumentView.tsx            | 63 +++++++++++++++-------
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +-
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 -
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 -
 src/client/views/pdf/Annotation.tsx                |  1 -
 12 files changed, 124 insertions(+), 47 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 42a78a4bf..6bb44f7d6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -83,6 +83,7 @@ export namespace DragManager {
     }
     export let AbortDrag: () => void = emptyFunction;
     export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    export type RemoveFunction = (document: Doc) => boolean;
 
     export interface DragDropDisposer { (): void; }
     export interface DragOptions {
@@ -138,6 +139,7 @@ export namespace DragManager {
         userDropAction: dropActionType;
         embedDoc?: boolean;
         moveDocument?: MoveFunction;
+        removeDocument?: RemoveFunction;
         isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
     }
     export class LinkDragData {
@@ -351,12 +353,17 @@ export namespace DragManager {
 
         let lastX = downX;
         let lastY = downY;
+        let alias = "alias";
         const moveHandler = (e: PointerEvent) => {
             e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
             }
             if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
+                !dragData.dropAction && (dragData.dropAction = alias);
+                if (dragData.dropAction === "move") {
+                    dragData.removeDocument?.(dragData.draggedDocuments[0]);
+                }
                 AbortDrag();
                 finishDrag?.(new DragCompleteEvent(true, dragData));
                 CollectionDockingView.Instance.StartOtherDrag({
@@ -366,7 +373,7 @@ export namespace DragManager {
                     button: 0
                 }, dragData.droppedDocuments);
             }
-            //TODO: Why can't we use e.movementX and e.movementY?
+            alias = "move";
             const moveX = e.pageX - lastX;
             const moveY = e.pageY - lastY;
             lastX = e.pageX;
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 680f48f70..356f20ce6 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,7 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection } from "prosemirror-state";
 import { SelectionManager } from "./SelectionManager";
 import { Docs } from "../documents/Documents";
-import { NumCast, BoolCast, Cast } from "../../new_fields/Types";
+import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types";
 import { Doc } from "../../new_fields/Doc";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { Id } from "../../new_fields/FieldSymbols";
@@ -153,10 +153,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const layoutDoc = props.Document;
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (originalDoc instanceof Doc) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
             const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
             FormattedTextBox.SelectOnLoad = newDoc[Id];
             props.addDocument(newDoc);
         }
@@ -171,10 +177,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const layoutDoc = props.Document;
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
             const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
             FormattedTextBox.SelectOnLoad = newDoc[Id];
             props.addDocument(newDoc);
             return true;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 4d04d4e89..5b66b63ed 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -99,6 +99,15 @@ export class ContextMenu extends React.Component {
         }
     }
     @action
+    moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
+        if (this.findByDescription(after.description)) {
+            const curInd = this._items.findIndex((i) => i.description === item.description);
+            this._items.splice(curInd, 1);
+            const afterInd = this._items.findIndex((i) => i.description === after.description);
+            this._items.splice(afterInd + 1, 0, item);
+        }
+    }
+    @action
     setDefaultItem(prefix: string, item: (name: string) => void) {
         this._defaultPrefix = prefix;
         this._defaultItem = item;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 4c84a22ad..665ab4e41 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -14,6 +14,7 @@ import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZer
 import { Transform } from "../util/Transform";
 import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { Scripting } from "../util/Scripting";
+import { List } from "../../new_fields/List";
 
 @observer
 class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
@@ -106,8 +107,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
     return100 = () => 100;
     @computed get scriptField() {
-        return ScriptField.MakeScript("switchView(firstDoc, this)", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
-            { firstDoc: this.props.docViews[0].props.Document });
+        return ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
+            { docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
     }
     render() {
         const firstDoc = this.props.docViews[0].props.Document;
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 511efe0a7..410b16ec7 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -11,6 +11,7 @@ import "./CollectionPileView.scss";
 import React = require("react");
 import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
+import { UndoManager } from "../../util/UndoManager";
 
 @observer
 export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -34,7 +35,10 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     layoutEngine = () => this._layoutEngine;
 
     @computed get contents() {
-        return <div className="collectionPileView-innards" style={{ width: "100%", pointerEvents: this._contentsActive && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none" }} >
+        return <div className="collectionPileView-innards" style={{
+            width: "100%",
+            pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none"
+        }} >
             <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
         </div>;
     }
@@ -71,9 +75,32 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         }
     });
 
+    _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
+        let dist = 0;
         // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false, false); // this sets _doubleTap
+        setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+            if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
+                dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
+                if (dist > 100) {
+                    if (!this._undoBatch) {
+                        this._undoBatch = UndoManager.StartBatch("layout pile");
+                    }
+                    const doc = this.childDocs[0];
+                    doc.x = e.clientX;
+                    doc.y = e.clientY;
+                    this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
+                    dist = 0;
+                }
+            }
+            return false;
+        }, () => {
+            this._undoBatch?.end();
+            this._undoBatch = undefined;
+            if (!this.childDocs.length) {
+                this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+            }
+        }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap
     }
 
     onClick = (e: React.MouseEvent) => {
@@ -81,10 +108,11 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
             SelectionManager.DeselectAll();
             this.toggleStarburst();
             e.stopPropagation();
-        } else if (this.layoutEngine() === "pass") {
-            runInAction(() => this._contentsActive = false);
-            setTimeout(action(() => this._contentsActive = true), 300);
         }
+        // else if (this.layoutEngine() === "pass") {
+        //     runInAction(() => this._contentsActive = false);
+        //     setTimeout(action(() => this._contentsActive = true), 300);
+        // }
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f82c6d8a6..2bd37bda5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -225,7 +225,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
 
             this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
-            this.setupViewTypes("New Perspective...", vtype => {
+            this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
                 this.props.addDocTab(newRendition, "onRight");
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index af9d3c5be..6a3764bb7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -821,18 +821,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (!willZoom && DocumentView._focusHack.length) {
-                Doc.BrushDoc(this.props.Document);
-                !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
-            } else {
-                if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
-                    if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
-                }
-                Doc.BrushDoc(this.props.Document);
-                this.props.focus(this.props.Document);
-                willZoom && this.setScaleToZoom(layoutdoc, scale);
+            // if (!willZoom && DocumentView._focusHack.length) {
+            //     Doc.BrushDoc(this.props.Document);
+            //     !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
+            // } else {
+            if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+                if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             }
+            Doc.BrushDoc(this.props.Document);
+            this.props.focus(this.props.Document);
+            willZoom && this.setScaleToZoom(layoutdoc, scale);
             Doc.linkFollowHighlight(doc);
+            //}
 
             afterFocus && setTimeout(() => {
                 if (afterFocus?.()) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 06bd40992..f9d9ca9a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -234,6 +234,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
+            dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;//  this.Document.onDragStart ? undefined : this.props.moveDocument;
             dragData.dragDivName = this.props.dragDivName;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
@@ -309,19 +310,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
                 UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
                 //ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
-            } else if (this.Document.isLinkButton) {
+            } else if (this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
-                    if (this.props.Document.type === DocumentType.RTF) {
-                        DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
-                        DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
+                    // if (this.props.Document.type === DocumentType.RTF) {
+                    //     DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
+                    //     DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
 
-                        this.props.focus(this.props.Document, false);
-                    }
-                    SelectionManager.SelectDoc(this, e.ctrlKey);
+                    //     this.props.focus(this.props.Document, false);
+                    // }
+                    SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
                 }
                 preventDefault = false;
             }
@@ -329,7 +330,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             preventDefault && e.preventDefault();
         }
     }
-    static _focusHack: number[] = []; // bcz :this will get fixed...
 
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
@@ -344,11 +344,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.  
                     return false; // we must return false here so that the zoom to the document is not reversed.  If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
                 };
-                this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
+                this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
                 return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
             };
             // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-            this.props.focus(this.props.Document, true, 1, targetFocusAfterDocFocus);
+            this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
         };
         await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
@@ -569,6 +569,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.Document.onClick = undefined;
         } else {
             this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = false;
             this.Document.followLinkLocation = undefined;
         }
     }
@@ -579,10 +580,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.Document.isLinkButton = false;
         } else {
             this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = true;
             this.Document.followLinkLocation = "inPlace";
         }
     }
 
+    @undoBatch
+    toggleFollowOnRight = (): void => {
+        if (this.Document.isLinkButton) {
+            this.Document.isLinkButton = false;
+        } else {
+            this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = false;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = true);
+            this.Document.followLinkLocation = "onRight";
+        }
+    }
+
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -679,20 +694,27 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
 
-        const existing = cm.findByDescription("Options...");
-        const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-        layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
-        layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-        layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
-        !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "compass" });
-
-        const open = cm.findByDescription("New Perspective...");
+        let open = cm.findByDescription("Add a Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
         openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        !open && cm.addItem({ description: "New Perspective...", subitems: openItems, icon: "external-link-alt" });
+        if (!open) {
+            open = { description: "Add a Perspective....", subitems: openItems, icon: "external-link-alt" };
+            cm.addItem(open);
+        }
+
+        let options = cm.findByDescription("Options...");
+        const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+        optionItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+        optionItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+        optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+        optionItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+        if (!options) {
+            options = { description: "Options...", subitems: optionItems, icon: "compass" };
+            cm.addItem(options);
+        }
 
+        cm.moveAfter(options, open);
 
         const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -700,6 +722,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
+        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a018f17e8..d7cfbae9f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -444,7 +444,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
 
-        const open = cm.findByDescription("New Perspective...");
+        const open = cm.findByDescription("Add a Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
 
         openItems.push({
@@ -454,7 +454,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 this.props.addDocTab(alias, "onRight");
             }), icon: "eye"
         });
-        !open && cm.addItem({ description: "New Perspective...", subitems: openItems, icon: "external-link-alt" });
+        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
 
     }
 
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 41df5b3c1..a9f76ae8f 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -88,7 +88,6 @@ export class FormattedTextBoxComment {
                     if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
                         textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
                     } else {
-                        DocumentView._focusHack = [];
                         DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
                             (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
                     }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index eb647d0e4..707b9d12a 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -75,7 +75,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    DocumentView._focusHack = [];
                     DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index eaf80d252..672d3adb8 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -98,7 +98,6 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentView._focusHack = [];
                 DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
                 e.stopPropagation();
             }
-- 
cgit v1.2.3-70-g09d2


From c6d8ac7eef3480a7e19687b066456d9d34519587 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 20:25:50 -0400
Subject: several fixes for performance and for working with isBackground
 objects.

---
 .../CollectionStackingViewFieldColumn.tsx           |  2 +-
 src/client/views/collections/CollectionView.tsx     |  2 +-
 .../CollectionFreeFormLayoutEngines.tsx             |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 21 ++++++++++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx  |  4 ++--
 src/client/views/nodes/DocumentView.tsx             | 21 ++++++++++++++-------
 src/client/views/nodes/ImageBox.scss                |  9 +--------
 src/client/views/nodes/ImageBox.tsx                 |  8 ++++----
 src/new_fields/util.ts                              |  2 +-
 9 files changed, 39 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 5d926b7c7..8992de9d1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -364,7 +364,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             <div className="collectionStackingViewFieldColumn" key={heading}
                 style={{
                     width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
-                    height: SelectionManager.GetIsDragging() ? "100%" : undefined,
+                    height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined,
                     background: this._background
                 }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2bd37bda5..801704673 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -493,7 +493,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
                 <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
             }
-            {this.filterView}
+            {this.facetWidth() < 10 ? (null) : this.filterView}
         </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9ca747113..9a864078a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -39,7 +39,7 @@ export interface PoolData {
     color?: string;
     transition?: string;
     highlight?: boolean;
-    replica?: string;
+    replica: string;
     pair: { layout: Doc, data?: Doc };
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6a3764bb7..a13c81a15 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -508,7 +508,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) {
                 const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
-                this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+                this.scaleAtPt(docpt, 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -853,7 +853,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
     @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
-
+    @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+    parentActive = () => this.props.active() || this.backgroundActive ? true : false;
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
@@ -879,7 +880,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
             backgroundHalo: this.backgroundHalo,
-            parentActive: this.props.active,
+            parentActive: this.parentActive,
             bringToFront: this.bringToFront,
             addDocTab: this.addDocTab,
         };
@@ -946,7 +947,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: Opt<string>) {
+    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
         return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
 
@@ -975,6 +976,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @computed get doInternalLayoutComputation() {
+        TraceMobx();
+
+
         const newPool = new Map<string, PoolData>();
         const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
         switch (engine) {
@@ -1009,8 +1013,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true)) ?
-                        false : undefined}
+                    pointerEvents={
+                        this.backgroundActive ?
+                            true :
+                            (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
                     jitterRotation={NumCast(this.props.Document._jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1164,6 +1170,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
+    @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1179,7 +1186,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
-                pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+                pointerEvents: this.backgroundEvents ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
                 transformOrigin: this.contentScaling ? "left top" : "",
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index baeac311e..7ef8e43f7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica?: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -23,7 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     transition?: string;
     fitToBox?: boolean;
-    replica?: string;
+    replica: string;
 }
 
 @observer
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9d9ca9a1..7ae77fc65 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -281,12 +281,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    onClick = (e: React.MouseEvent | React.PointerEvent) => {
+    onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
-            this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
+            !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (!(e.nativeEvent as any).formattedHandled) {
                     const fullScreenAlias = Doc.MakeAlias(this.props.Document);
@@ -329,7 +329,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
         }
-    }
+    });
 
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
@@ -347,8 +347,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
                 return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
             };
-            // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-            this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            if (!this.Document.followLinkZoom) {
+                targetFocusAfterDocFocus();
+            } else {
+                // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
+                this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            }
         };
         await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
@@ -642,6 +646,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
             DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
         }
+        this.Document.followLinkZoom = true;
         this.Document.isLinkButton = true;
     }
 
@@ -1069,7 +1074,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const titleView = (!showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
                 position: showTextTitle ? "relative" : "absolute",
-                pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
                     contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
@@ -1089,7 +1094,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>;
     }
     @computed get ignorePointerEvents() {
-        return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+        return this.props.pointerEvents === false ||
+            (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) ||
+            (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
     @undoBatch
     @action
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 49425c2c2..15148d01d 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,5 +1,4 @@
-.imageBox,
-.imageBox-dragging {
+.imageBox {
     border-radius: inherit;
     width: 100%;
     height: 100%;
@@ -11,12 +10,6 @@
     }
 }
 
-.imageBox-dragging {
-    .imageBox-fader {
-        pointer-events: none;
-    }
-}
-
 #upload-icon {
     position: absolute;
     bottom: 0;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e189f5814..14b367868 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -360,7 +360,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     }
 
     @computed get nativeSize() {
-        const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
+        TraceMobx();
+        const pw = this.props.PanelWidth?.() || 50;
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], pw);
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1);
         return { nativeWidth, nativeHeight };
@@ -374,7 +375,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     @computed get paths() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
-        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url.href).filter(url => url); // access the primary layout data of the alternate documents
+        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
     }
@@ -438,8 +439,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     contentFunc = () => [this.content];
     render() {
         TraceMobx();
-        const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
-        return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
+        return (<div className={`imageBox`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: this.props.PanelWidth() ? `translate(0px, ${this.ycenter}px)` : `scale(${this.props.ContentScaling()})`,
                 width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2

