From 0f4b9e541e4e1332bbed95f4a99113162ffef806 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 30 Apr 2020 11:10:27 -0400
Subject: added double click script support

---
 src/client/views/collections/CollectionView.tsx | 7 ++++---
 1 file changed, 4 insertions(+), 3 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 801704673..2c52097aa 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -247,13 +247,14 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
             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 }];
+            const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField },
+            { key: "onChildDoubleClick", name: "On Child Double Clicked", script: undefined as any as ScriptField }];
             DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick =>
-                funcs.push({ key: "onChildClick", name: StrCast(childClick.title), script: ScriptCast(childClick.script) }));
+                funcs.push({ key: StrCast(childClick.scriptKey), name: StrCast(childClick.title), script: ScriptCast(childClick.data) }));
             funcs.map(func => onClicks.push({
                 description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
                     func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script));
-                    ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
+                    ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }, func.script);
                 }
             }));
             !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
-- 
cgit v1.2.3-70-g09d2


From d4dd4ccd299ba2e06b35a6f14f698a26d026aeee Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 30 Apr 2020 11:34:08 -0400
Subject: clean up of childclick template func menus

---
 src/client/documents/Documents.ts                      |  2 +-
 src/client/views/collections/CollectionView.tsx        | 16 ++++++++++------
 src/server/authentication/models/current_user_utils.ts | 10 +++++++---
 3 files changed, 18 insertions(+), 10 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 436e59daf..0809ae24f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -95,7 +95,7 @@ export interface DocumentOptions {
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
     isTemplateDoc?: boolean;
-    scriptKey?: string; // the script key that a child click func script template document will write into
+    targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
     templates?: List<string>;
     backgroundColor?: string | ScriptField;  // background color for data doc 
     _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor )
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2c52097aa..79bb6c41b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -247,16 +247,20 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
             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 },
-            { key: "onChildDoubleClick", name: "On Child Double Clicked", script: undefined as any as ScriptField }];
-            DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick =>
-                funcs.push({ key: StrCast(childClick.scriptKey), name: StrCast(childClick.title), script: ScriptCast(childClick.data) }));
+            const funcs = [
+                { key: "onChildClick", name: "On Child Clicked" },
+                { key: "onChildDoubleClick", name: "On Child Double Clicked" }];
             funcs.map(func => onClicks.push({
                 description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
-                    func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script));
-                    ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }, func.script);
+                    ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
                 }
             }));
+            DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick =>
+                onClicks.push({
+                    description: `Set child ${childClick.title}`,
+                    icon: "edit",
+                    event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
+                }));
             !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
             const more = ContextMenu.Instance.findByDescription("More...");
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 1be977e4f..2c861d4fa 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -593,12 +593,16 @@ export class CurrentUserUtils {
     static setupClickEditorTemplates(doc: Doc) {
         if (doc.childClickFuncs === undefined) {
             const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
-                "docCast(thisContainer.target).then((target) => { target && docCast(this.source).then((source) => { target.proto.data = new List([source || this]); } ); } )",
-                { target: Doc.name }), { title: "On Child Clicked (open in target)", _width: 300, _height: 200, scriptKey: "onChildClick" });
+                "docCast(thisContainer.target).then((target) => {" +
+                "     target && docCast(this.source).then((source) => { " +
+                "           target.proto.data = new List([source || this]); " +
+                "      }); " +
+                "})",
+                { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" });
 
             const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
                 "openOnRight(self.doubleClickView)",
-                { target: Doc.name }), { title: "On Child Dbl Clicked (open double click view)", _width: 300, _height: 200, scriptKey: "onChildDoubleClick" });
+                { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
 
             doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
         }
-- 
cgit v1.2.3-70-g09d2


From baae91e7829676f03878696e9b13e1bdb4fb3c33 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 30 Apr 2020 11:40:57 -0400
Subject: from last

---
 src/client/views/collections/CollectionView.tsx        | 2 +-
 src/server/authentication/models/current_user_utils.ts | 6 +++---
 2 files changed, 4 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 79bb6c41b..8d8c321e8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -255,7 +255,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
                 }
             }));
-            DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick =>
+            DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
                 onClicks.push({
                     description: `Set child ${childClick.title}`,
                     icon: "edit",
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 2c861d4fa..e49cc4804 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -591,7 +591,7 @@ export class CurrentUserUtils {
     }
 
     static setupClickEditorTemplates(doc: Doc) {
-        if (doc.childClickFuncs === undefined) {
+        if (doc["clickFuncs-child"] === undefined) {
             const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
                 "docCast(thisContainer.target).then((target) => {" +
                 "     target && docCast(this.source).then((source) => { " +
@@ -604,10 +604,10 @@ export class CurrentUserUtils {
                 "openOnRight(self.doubleClickView)",
                 { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
 
-            doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
+            doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
         }
         // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
-        PromiseValue(Cast(doc.childClickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
+        PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
 
         if (doc.clickFuncs === undefined) {
             const onClick = Docs.Create.ScriptingDocument(undefined, {
-- 
cgit v1.2.3-70-g09d2


From ff7c7d40b1fcdf74b539c7d97f36707ff1521d2e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 1 May 2020 01:46:07 -0400
Subject: fixed presentations to allow drag and drop.  fixed pres box to use
 RenderData instead of modifying presentation elements with unnecessary info
 like their containing PresBox and their presentation index position. 
 COnverted COntentFIttingDocumentView to use DocumentView's props

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/views/MainView.tsx                      |  4 +-
 src/client/views/SearchDocBox.tsx                  | 10 ++++-
 .../views/collections/CollectionCarouselView.tsx   |  8 +++-
 .../views/collections/CollectionSchemaView.tsx     | 14 ++++---
 .../views/collections/CollectionStackingView.tsx   | 15 ++++---
 .../views/collections/CollectionTreeView.tsx       | 20 +++++----
 src/client/views/collections/CollectionView.tsx    | 14 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 .../CollectionMulticolumnView.tsx                  | 14 ++++---
 .../CollectionMultirowView.tsx                     | 14 ++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  8 ++--
 .../views/nodes/ContentFittingDocumentView.tsx     | 49 ++++------------------
 src/client/views/nodes/DocumentBox.tsx             | 15 ++++---
 src/client/views/nodes/DocumentView.tsx            | 20 ++++-----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/PresBox.tsx                 | 21 ++++------
 .../formattedText/FormattedTextBoxComment.tsx      | 12 ++++--
 .../views/nodes/formattedText/RichTextMenu.scss    |  3 ++
 .../views/presentationview/PresElementBox.tsx      | 25 +++++++----
 src/client/views/search/SearchItem.tsx             | 12 ++++--
 src/new_fields/documentSchemas.ts                  |  2 +-
 .../authentication/models/current_user_utils.ts    |  2 +-
 23 files changed, 150 insertions(+), 137 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2e81d5fa6..228a6af97 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -81,6 +81,7 @@ export interface DocumentOptions {
     author?: string;
     dropAction?: dropActionType;
     childDropAction?: dropActionType;
+    targetDropAction?: dropActionType;
     layoutKey?: string;
     type?: string;
     title?: string;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 65e4eb036..a29a6baac 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -597,8 +597,8 @@ export class MainView extends React.Component {
             {// TO VIEW SNAP LINES
                 <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
                     <svg style={{ width: "100%", height: "100%" }}>
-                        {this._hLines?.map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
-                        {this._vLines?.map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                        {this._hLines?.map((l: any) => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+                        {this._vLines?.map((l: any) => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
                     </svg>
                 </div>}
             <TimelineMenu />
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
index 799fa9d85..7bd689b19 100644
--- a/src/client/views/SearchDocBox.tsx
+++ b/src/client/views/SearchDocBox.tsx
@@ -6,7 +6,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../new_fields/Doc";
 import { Id } from "../../new_fields/FieldSymbols";
 import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { returnFalse } from "../../Utils";
+import { returnFalse, returnZero } from "../../Utils";
 import { Docs } from "../documents/Documents";
 import { SearchUtil } from "../util/SearchUtil";
 import { EditableView } from "./EditableView";
@@ -399,7 +399,13 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
                 <ContentFittingDocumentView {...this.props}
                     Document={this.content}
                     rootSelected={returnFalse}
-                    getTransform={this.props.ScreenToLocalTransform}>
+                    bringToFront={returnFalse}
+                    ContainingCollectionDoc={undefined}
+                    ContainingCollectionView={undefined}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
+                    parentActive={this.props.active}
+                    ScreenToLocalTransform={this.props.ScreenToLocalTransform}>
                 </ContentFittingDocumentView>
                 <div
                     style={{
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index eda8e5684..bfb7134b2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -14,6 +14,7 @@ import { Doc } from '../../../new_fields/Doc';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
+import { returnFalse } from '../../../Utils';
 
 type CarouselDocument = makeInterface<[typeof documentSchema,]>;
 const CarouselDocument = makeInterface(documentSchema);
@@ -49,9 +50,12 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                     <ContentFittingDocumentView {...this.props}
                         renderDepth={this.props.renderDepth + 1}
                         Document={this.childLayoutPairs[index].layout}
-                        DataDocument={this.childLayoutPairs[index].data}
+                        DataDoc={this.childLayoutPairs[index].data}
                         PanelHeight={this.panelHeight}
-                        getTransform={this.props.ScreenToLocalTransform} />
+                        ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                        bringToFront={returnFalse}
+                        parentActive={this.props.active}
+                    />
                 </div>
                 <div className="collectionCarouselView-caption" key="caption"
                     style={{
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 380d91d2f..c0024293f 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -27,7 +27,7 @@ import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionView } from "./CollectionView";
 import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
+import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
 import { DocumentView } from "../nodes/DocumentView";
 
 library.add(faCog, faPlus, faSortUp, faSortDown);
@@ -121,7 +121,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             {!this.previewDocument ? (null) :
                 <ContentFittingDocumentView
                     Document={this.previewDocument}
-                    DataDocument={undefined}
+                    DataDoc={undefined}
                     NativeHeight={returnZero}
                     NativeWidth={returnZero}
                     fitToBox={true}
@@ -132,16 +132,18 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                     rootSelected={this.rootSelected}
                     PanelWidth={this.previewWidth}
                     PanelHeight={this.previewHeight}
-                    getTransform={this.getPreviewTransform}
-                    CollectionDoc={this.props.CollectionView?.props.Document}
-                    CollectionView={this.props.CollectionView}
+                    ScreenToLocalTransform={this.getPreviewTransform}
+                    ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+                    ContainingCollectionView={this.props.CollectionView}
                     moveDocument={this.props.moveDocument}
                     addDocument={this.props.addDocument}
                     removeDocument={this.props.removeDocument}
-                    active={this.props.active}
+                    parentActive={this.props.active}
                     whenActiveChanged={this.props.whenActiveChanged}
                     addDocTab={this.props.addDocTab}
                     pinToPres={this.props.pinToPres}
+                    bringToFront={returnFalse}
+                    ContentScaling={returnOne}
                 />}
         </div>;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 556d7df5c..6c230d5b1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
+import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
@@ -165,12 +165,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         const height = () => this.getDocHeight(doc);
         return <ContentFittingDocumentView
             Document={doc}
-            DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+            DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
+            RenderData={this.props.RenderData}
             PanelWidth={width}
             PanelHeight={height}
             NativeHeight={returnZero}
@@ -180,16 +181,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
-            getTransform={dxf}
+            ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            CollectionDoc={this.props.CollectionView?.props.Document}
-            CollectionView={this.props.CollectionView}
+            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+            ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
-            active={this.props.active}
+            parentActive={this.props.active}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.addDocTab}
+            bringToFront={returnFalse}
+            ContentScaling={returnOne}
             pinToPres={this.props.pinToPres}
         />;
     }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d938bd7ad..71358a8ec 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -353,27 +353,31 @@ class TreeView extends React.Component<TreeViewProps> {
             return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}>
                 <ContentFittingDocumentView
                     Document={layoutDoc}
-                    DataDocument={this.templateDataDoc}
+                    DataDoc={this.templateDataDoc}
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
                     rootSelected={returnTrue}
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     FreezeDimensions={true}
-                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
-                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : returnZero}
+                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : returnZero}
                     PanelWidth={panelWidth}
                     PanelHeight={panelHeight}
-                    getTransform={this.docTransform}
-                    CollectionDoc={this.props.containingCollection}
-                    CollectionView={undefined}
+                    focus={returnFalse}
+                    ScreenToLocalTransform={this.docTransform}
+                    ContainingCollectionDoc={this.props.containingCollection}
+                    ContainingCollectionView={undefined}
                     addDocument={returnFalse}
                     moveDocument={this.props.moveDocument}
                     removeDocument={returnFalse}
-                    active={this.props.active}
+                    parentActive={this.props.active}
                     whenActiveChanged={emptyFunction}
                     addDocTab={this.props.addDocTab}
-                    pinToPres={this.props.pinToPres} />
+                    pinToPres={this.props.pinToPres}
+                    bringToFront={returnFalse}
+                    ContentScaling={returnOne}
+                />
             </div>;
         }
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8d8c321e8..561226de5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -113,12 +113,16 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = this.props.Document[DataSym];
-        const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
-        !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc]));  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
-        // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
+        if (this.props.addDocument) {
+            this.props.addDocument(doc);
+        } else {
+            const targetDataDoc = this.props.Document[DataSym];
+            const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
+            !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc]));  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
+            // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
+            targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+        }
         doc.context = this.props.Document;
-        targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
         return true;
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 763a6c605..b4eb22444 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -877,6 +877,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onDoubleClick: this.onChildDoubleClickHandler,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
+            RenderData: this.props.RenderData,
             PanelWidth: childLayout[WidthSym],
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
@@ -1176,7 +1177,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
     onPointerOver = (e: React.PointerEvent) => {
         if (SelectionManager.GetIsDragging()) {
-            this.setupDragLines(e);
+            this.setupDragLines();
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 66d441115..b3a6a9deb 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -14,7 +14,7 @@ import "./collectionMulticolumnView.scss";
 import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
 import { List } from '../../../../new_fields/List';
-import { returnZero } from '../../../../Utils';
+import { returnZero, returnFalse, returnOne } from '../../../../Utils';
 
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
@@ -216,7 +216,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
         return <ContentFittingDocumentView
             Document={layout}
-            DataDocument={layout.resolvedDataDoc as Doc}
+            DataDoc={layout.resolvedDataDoc as Doc}
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
@@ -231,17 +231,19 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
-            getTransform={dxf}
+            ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            CollectionDoc={this.props.CollectionView?.props.Document}
-            CollectionView={this.props.CollectionView}
+            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+            ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
-            active={this.props.active}
+            parentActive={this.props.active}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.addDocTab}
             pinToPres={this.props.pinToPres}
+            bringToFront={returnFalse}
+            ContentScaling={returnOne}
         />;
     }
     /**
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 615efdb39..0fb29ca61 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -6,7 +6,7 @@ import * as React from "react";
 import { Doc } from '../../../../new_fields/Doc';
 import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types';
 import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
-import { Utils, returnZero } from '../../../../Utils';
+import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils';
 import "./collectionMultirowView.scss";
 import { computed, trace, observable, action } from 'mobx';
 import { Transform } from '../../../util/Transform';
@@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
         return <ContentFittingDocumentView
             Document={layout}
-            DataDocument={layout.resolvedDataDoc as Doc}
+            DataDoc={layout.resolvedDataDoc as Doc}
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
@@ -230,17 +230,19 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
-            getTransform={dxf}
+            ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            CollectionDoc={this.props.CollectionView?.props.Document}
-            CollectionView={this.props.CollectionView}
+            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+            ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
-            active={this.props.active}
+            parentActive={this.props.active}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.addDocTab}
             pinToPres={this.props.pinToPres}
+            bringToFront={returnFalse}
+            ContentScaling={returnOne}
         />;
     }
     /**
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 1c7d116c5..24468dcc1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -115,13 +115,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight} />
                 : <ContentFittingDocumentView {...this.props}
-                    CollectionDoc={this.props.ContainingCollectionDoc}
-                    DataDocument={this.props.DataDoc}
-                    getTransform={this.getTransform}
+                    ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                    DataDoc={this.props.DataDoc}
+                    ScreenToLocalTransform={this.getTransform}
                     NativeHeight={this.NativeHeight}
                     NativeWidth={this.NativeWidth}
-                    active={this.props.parentActive}
-                    focus={this.focusDoc}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
                 />}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index d0b0c8ee6..637fd5acc 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -3,51 +3,16 @@ import { computed } from "mobx";
 import { observer } from "mobx-react";
 import "react-table/react-table.css";
 import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
-import { ScriptField } from "../../../new_fields/ScriptField";
 import { NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
 import { emptyFunction, returnOne } from "../../../Utils";
-import { Transform } from "../../util/Transform";
-import { CollectionView } from "../collections/CollectionView";
 import '../DocumentDecorations.scss';
-import { DocumentView } from "../nodes/DocumentView";
+import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import "./ContentFittingDocumentView.scss";
-import { dropActionType } from "../../util/DragManager";
 
-interface ContentFittingDocumentViewProps {
-    Document: Doc;
-    DataDocument?: Doc;
-    LayoutDoc?: () => Opt<Doc>;
-    NativeWidth?: () => number;
-    NativeHeight?: () => number;
-    FreezeDimensions?: boolean;
-    LibraryPath: Doc[];
-    renderDepth: number;
-    fitToBox?: boolean;
-    layoutKey?: string;
-    dropAction?: dropActionType;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
-    focus?: (doc: Doc) => void;
-    CollectionView?: CollectionView;
-    CollectionDoc?: Doc;
-    onClick?: ScriptField;
-    onDoubleClick?: ScriptField;
-    backgroundColor?: (doc: Doc) => string | undefined;
-    getTransform: () => Transform;
-    addDocument?: (document: Doc) => boolean;
-    moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
-    removeDocument?: (document: Doc) => boolean;
-    active: (outsideReaction: boolean) => boolean;
-    whenActiveChanged: (isActive: boolean) => void;
-    addDocTab: (document: Doc, where: string) => boolean;
-    pinToPres: (document: Doc) => void;
-    dontRegisterView?: boolean;
-    rootSelected: (outsideReaction?: boolean) => boolean;
-}
 
 @observer
-export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
+export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
@@ -68,7 +33,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     @computed get panelWidth() { return this.nativeWidth && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
     @computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
-    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
+    private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
     private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
     private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
 
@@ -90,7 +55,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                     }}>
                     <DocumentView {...this.props}
                         Document={this.props.Document}
-                        DataDoc={this.props.DataDocument}
+                        DataDoc={this.props.DataDoc}
                         LayoutDoc={this.props.LayoutDoc}
                         LibraryPath={this.props.LibraryPath}
                         NativeWidth={this.nativeWidth}
@@ -107,11 +72,11 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         removeDocument={this.props.removeDocument}
                         moveDocument={this.props.moveDocument}
                         whenActiveChanged={this.props.whenActiveChanged}
-                        ContainingCollectionView={this.props.CollectionView}
-                        ContainingCollectionDoc={this.props.CollectionDoc}
+                        ContainingCollectionView={this.props.ContainingCollectionView}
+                        ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                         addDocTab={this.props.addDocTab}
                         pinToPres={this.props.pinToPres}
-                        parentActive={this.props.active}
+                        parentActive={this.props.parentActive}
                         ScreenToLocalTransform={this.getTransform}
                         renderDepth={this.props.renderDepth}
                         focus={this.props.focus || emptyFunction}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index d4d997120..88eeb1137 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -6,7 +6,7 @@ import { documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyPath } from "../../../Utils";
+import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
@@ -114,9 +114,10 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         }
         const contents = !(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView
             Document={containedDoc}
-            DataDocument={undefined}
+            DataDoc={undefined}
             LibraryPath={emptyPath}
-            CollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
+            ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
+            ContainingCollectionDoc={undefined}
             fitToBox={true}
             layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
             rootSelected={this.props.isSelected}
@@ -125,14 +126,18 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
             removeDocument={this.props.removeDocument}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}
-            getTransform={this.getTransform}
+            ScreenToLocalTransform={this.getTransform}
             renderDepth={this.props.renderDepth + 1}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
             PanelWidth={this.pwidth}
             PanelHeight={this.pheight}
             focus={this.props.focus}
-            active={this.props.active}
+            parentActive={this.props.active}
             dontRegisterView={!this.isSelectionLocked()}
             whenActiveChanged={this.props.whenActiveChanged}
+            bringToFront={returnFalse}
+            ContentScaling={returnOne}
         />;
         return contents;
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 085637440..f555d6eef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -77,6 +77,7 @@ export interface DocumentViewProps {
     setupDragLines?: () => void;
     renderDepth: number;
     ContentScaling: () => number;
+    RenderData?: () => Doc;
     PanelWidth: () => number;
     PanelHeight: () => number;
     pointerEvents?: boolean;
@@ -992,6 +993,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 LayoutDoc={this.props.LayoutDoc}
                 makeLink={this.makeLink}
                 rootSelected={this.rootSelected}
+                RenderData={this.props.RenderData}
                 dontRegisterView={this.props.dontRegisterView}
                 fitToBox={this.props.fitToBox}
                 LibraryPath={this.props.LibraryPath}
@@ -1112,17 +1114,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
         }
     }
-    @observable _animate = 0;
+    @observable _animateScalingTo = 0;
     switchViews = action((custom: boolean, view: string) => {
-        SelectionManager.SetIsDragging(true);
-        this._animate = 0.1;
+        this._animateScalingTo = 0.1;  // shrink doc
         setTimeout(action(() => {
             this.setCustomView(custom, view);
-            this._animate = 1;
-            setTimeout(action(() => {
-                this._animate = 0;
-                SelectionManager.SetIsDragging(false);
-            }), 400);
+            this._animateScalingTo = 1; // expand it
+            setTimeout(action(() => this._animateScalingTo = 0), 400);
         }), 400);
     });
 
@@ -1156,9 +1154,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 !entered && Doc.UnBrushDoc(this.props.Document);
             })}
             style={{
-                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",
+                transformOrigin: this._animateScalingTo ? "center center" : undefined,
+                transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
+                transition: !this._animateScalingTo ? StrCast(this.Document.transition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
                 pointerEvents: this.ignorePointerEvents ? "none" : undefined,
                 color: StrCast(this.layoutDoc.color, "inherit"),
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 0b9edbcd3..1efee4f5a 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -56,6 +56,7 @@ export interface FieldViewProps {
     width?: number;
     background?: string;
     color?: string;
+    RenderData?: () => Doc;
 }
 
 @observer
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index f91a809bb..3fcc97473 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -16,6 +16,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./PresBox.scss";
 import { ViewBoxBaseComponent } from "../DocComponent";
 import { makeInterface } from "../../../new_fields/Schema";
+import { List } from "../../../new_fields/List";
 
 type PresBoxSchema = makeInterface<[typeof documentSchema]>;
 const PresBoxDocument = makeInterface(documentSchema);
@@ -23,20 +24,15 @@ const PresBoxDocument = makeInterface(documentSchema);
 @observer
 export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
-    private _childReaction: IReactionDisposer | undefined;
     @observable _isChildActive = false;
     @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
     @computed get currentIndex() { return NumCast(this.rootDoc._itemIndex); }
 
     componentDidMount() {
+        this.rootDoc.presBox = this.rootDoc;
         this.rootDoc._forceRenderEngine = "timeline";
         this.rootDoc._replacedChrome = "replaced";
-        this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
     }
-    componentWillUnmount() {
-        this._childReaction?.();
-    }
-
     updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc;
 
     @undoBatch
@@ -247,16 +243,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
 
     initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => {
         const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
-        docList.forEach(doc => {
-            doc.presBox = this.rootDoc; // give contained documents a reference to the presentation
-            doc.presCollapsedHeight = hgt;  //  set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din
-        });
+        this.rootDoc.presCollapsedHeight = hgt;
     }
 
     addDocument = (doc: Doc) => {
-        const newPinDoc = Doc.MakeAlias(doc);
-        newPinDoc.presentationTargetDoc = doc;
-        return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc);
+        doc.presentationTargetDoc = doc.aliasOf;
+        return Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
     }
 
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
@@ -280,8 +272,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         this.updateMinimize(e, this.rootDoc._viewType = viewType);
     });
 
+    returnSelf = () => this.rootDoc;
     childLayoutTemplate = () => this.rootDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined;
     render() {
+        this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.map((child, i) => child));
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
         this.initializeViewAliases(this.childDocs, mode);
         return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
@@ -314,6 +308,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                         childLayoutTemplate={this.childLayoutTemplate}
                         addDocument={this.addDocument}
                         removeDocument={returnFalse}
+                        RenderData={this.returnSelf}
                         focus={this.selectElement}
                         ScreenToLocalTransform={this.getTransform} />
                     : (null)
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index f9e4c5210..9ad5aafb8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
 import { Doc, DocCastAsync } from "../../../../new_fields/Doc";
 import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { schema } from "./schema_rts";
@@ -192,18 +192,24 @@ export class FormattedTextBoxComment {
                                     fitToBox={true}
                                     moveDocument={returnFalse}
                                     rootSelected={returnFalse}
-                                    getTransform={Transform.Identity}
-                                    active={returnFalse}
+                                    ScreenToLocalTransform={Transform.Identity}
+                                    parentActive={returnFalse}
                                     addDocument={returnFalse}
                                     removeDocument={returnFalse}
                                     addDocTab={returnFalse}
                                     pinToPres={returnFalse}
                                     dontRegisterView={true}
+                                    ContainingCollectionDoc={undefined}
+                                    ContainingCollectionView={undefined}
                                     renderDepth={1}
                                     PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
                                     PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
                                     focus={emptyFunction}
                                     whenActiveChanged={returnFalse}
+                                    bringToFront={returnFalse}
+                                    ContentScaling={returnOne}
+                                    NativeWidth={returnZero}
+                                    NativeHeight={returnZero}
                                 />, FormattedTextBoxComment.tooltipText);
                                 FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
                                 FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 3a16171de..7a0718c16 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -55,6 +55,9 @@
         color: black;
     }
 
+}
+
+.richTextMenu {
     select {
         background-color: #323232;
         color: white;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 66f251b93..1887c8d45 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -1,12 +1,12 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DataSym } from "../../../new_fields/Doc";
+import { Doc, DataSym, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from "../../../new_fields/FieldSymbols";
 import { createSchema, makeInterface } from '../../../new_fields/Schema';
 import { Cast, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionView';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -38,13 +38,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
 
     _heightDisposer: IReactionDisposer | undefined;
-    @computed get indexInPres() { return NumCast(this.rootDoc.presentationIndex); }
-    @computed get presBoxDoc() { return Cast(this.rootDoc.presBox, Doc) as Doc; }
+    @computed get indexInPres() { return DocListCast(this.presBoxDoc.presOrderedDocs).findIndex(d => d === this.rootDoc); }
+    @computed get presBoxDoc() { return Cast(this.props.RenderData?.().presBox, Doc) as Doc; }
     @computed get targetDoc() { return this.rootDoc.presentationTargetDoc as Doc; }
     @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); }
+    @computed get collapsedHeight() { return NumCast(this.presBoxDoc?.presCollapsedHeight); }
 
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.rootDoc.presCollapsedHeight],
+        this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
             params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
     }
     componentWillUnmount() {
@@ -147,7 +148,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
      */
     ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
 
-    embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - NumCast(this.rootDoc.presCollapsedHeight));
+    embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
     embedWidth = () => this.props.PanelWidth() - 20;
     /**
      * The function that is responsible for rendering the a preview or not for this
@@ -158,7 +159,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
             <div className="presElementBox-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
                 <ContentFittingDocumentView
                     Document={this.targetDoc}
-                    DataDocument={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+                    DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     LibraryPath={emptyPath}
                     fitToBox={true}
                     rootSelected={returnTrue}
@@ -168,12 +169,18 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                     pinToPres={returnFalse}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
-                    getTransform={Transform.Identity}
-                    active={this.props.active}
+                    ScreenToLocalTransform={Transform.Identity}
+                    parentActive={this.props.active}
                     moveDocument={this.props.moveDocument!}
                     renderDepth={this.props.renderDepth + 1}
                     focus={emptyFunction}
                     whenActiveChanged={returnFalse}
+                    bringToFront={returnFalse}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                 />
                 <div className="presElementBox-embeddedMask" />
             </div>;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index fe2000700..96f43e931 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -164,14 +164,20 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     removeDocument={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
-                    getTransform={Transform.Identity}
+                    ContainingCollectionDoc={undefined}
+                    ContainingCollectionView={undefined}
+                    ScreenToLocalTransform={Transform.Identity}
                     renderDepth={1}
                     PanelWidth={returnXDimension}
                     PanelHeight={returnYDimension}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
                     focus={emptyFunction}
                     moveDocument={returnFalse}
-                    active={returnFalse}
+                    parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
+                    bringToFront={returnFalse}
+                    ContentScaling={returnOne}
                 />
             </div>;
             return docview;
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 5ca0d681e..cd4b9d591 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -9,7 +9,7 @@ export const documentSchema = createSchema({
     layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
     title: "string",            // document title (can be on either data document or layout)
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-    targetDropAction: "string",  // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
+    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
     childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
     _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
     _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index d7cc1e6bf..f37538252 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -578,7 +578,7 @@ export class CurrentUserUtils {
         }
         if (doc.activePresentation === undefined) {
             doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), {
-                title: "Presentation", _viewType: CollectionViewType.Stacking,
+                title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias",
                 _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0"
             });
         }
-- 
cgit v1.2.3-70-g09d2


From 37324d134c8d788a73b8b1d35afa7ea6b0e88d37 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 1 May 2020 12:47:51 -0400
Subject: cleaned up buxton template.  fixed adding docs to collections.

---
 src/client/documents/Documents.ts                   |  7 +++----
 src/client/views/collections/CollectionPileView.tsx |  4 ++--
 .../views/collections/CollectionStackingView.scss   |  7 +++++--
 .../CollectionStackingViewFieldColumn.tsx           |  6 +-----
 src/client/views/collections/CollectionView.tsx     | 21 ++++++++++++---------
 .../collections/collectionFreeForm/MarqueeView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx             |  2 +-
 src/client/views/nodes/PresBox.tsx                  |  6 +++---
 src/new_fields/Doc.ts                               |  2 +-
 src/new_fields/documentSchemas.ts                   |  4 ++--
 .../authentication/models/current_user_utils.ts     |  7 ++++---
 11 files changed, 35 insertions(+), 33 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 228a6af97..3d790a485 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -98,6 +98,7 @@ export interface DocumentOptions {
     isTemplateDoc?: boolean;
     targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
     templates?: List<string>;
+    hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images)
     backgroundColor?: string | ScriptField;  // background color for data doc 
     _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor )
     color?: string; // foreground color data doc
@@ -423,11 +424,9 @@ export namespace Docs {
                         _nativeHeight: nativeHeight
                     }));
                     // the main document we create
-                    const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true });
-                    const deviceProto = Doc.GetProto(doc);
-                    deviceProto.hero = new ImageField(constructed[0].url);
+                    const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true, hero: new ImageField(constructed[0].url) });
                     // add the parsed attributes to this main document
-                    Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: deviceProto } });
+                    Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } });
                     Doc.AddDocToList(parentProto, "data", doc);
                 } else if (errors) {
                     console.log(errors);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 3bbfcc4d7..8b8cbc6e8 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -53,7 +53,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     toggleStarburst = action(() => {
         if (this._layoutEngine === 'starburst') {
             const defaultSize = 110;
-            this.layoutDoc.overflow = undefined;
+            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);
@@ -61,7 +61,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
             this._layoutEngine = 'pass';
         } else {
             const defaultSize = 25;
-            this.layoutDoc.overflow = 'visible';
+            this.layoutDoc._overflow = 'visible';
             !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
             if (this._layoutEngine === 'pass') {
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 47faa9239..5eaf29316 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -180,13 +180,16 @@
         .collectionStackingView-sectionHeader-subCont {
             outline: none;
             border: 0px;
-            color: $light-color; 
             width: 100%;
-            color: grey;
             letter-spacing: 2px;
             font-size: 75%;
             transition: transform 0.2s;
             position: relative;
+            height: 30px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: lightGray;
 
             .editableView-container-editing-oneLine,
             .editableView-container-editing {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 323d7be25..8a9539eb0 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -322,11 +322,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
                     title={evContents === `NO ${key.toUpperCase()} VALUE` ?
                         `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
-                    style={{
-                        width: "100%",
-                        background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit",
-                        color: "grey"
-                    }}>
+                    style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit" }}>
                     <EditableView {...headerEditableViewProps} />
                     {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
                         <div className="collectionStackingView-sectionColor">
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 561226de5..91018980c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -71,6 +71,9 @@ export enum CollectionViewType {
     Map = "map",
     Pile = "pileup"
 }
+export interface CollectionViewCustomProps {
+    filterAddDocument: (doc: Doc) => boolean;
+}
 
 export interface CollectionRenderProps {
     addDocument: (document: Doc) => boolean;
@@ -82,7 +85,7 @@ export interface CollectionRenderProps {
 }
 
 @observer
-export class CollectionView extends Touchable<FieldViewProps> {
+export class CollectionView extends Touchable<FieldViewProps & CollectionViewCustomProps> {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
 
     private _isChildActive = false;   //TODO should this be observable?
@@ -113,15 +116,15 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        if (this.props.addDocument) {
-            this.props.addDocument(doc);
-        } else {
-            const targetDataDoc = this.props.Document[DataSym];
-            const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
-            !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc]));  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
-            // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
-            targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+        if (this.props.filterAddDocument?.(doc) === false) {
+            return false;
         }
+
+        const targetDataDoc = this.props.Document[DataSym];
+        const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
+        !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc]));  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
+        // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
+        targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         doc.context = this.props.Document;
         Doc.GetProto(doc).lastOpened = new DateField;
         return true;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c70301b2f..5bac075b4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -613,7 +613,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     render() {
         return <div className="marqueeView"
-            style={{ overflow: StrCast(this.props.Document.overflow), }}
+            style={{ overflow: StrCast(this.props.Document._overflow), }}
             onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f555d6eef..f1efa48f4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -666,7 +666,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     toggleBackground = (temporary: boolean): void => {
-        this.Document.overflow = temporary ? "visible" : "hidden";
+        this.Document._overflow = temporary ? "visible" : "hidden";
         this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
         this.Document.isBackground && this.props.bringToFront(this.Document, true);
     }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 3fcc97473..72bbc9e4b 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -246,9 +246,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         this.rootDoc.presCollapsedHeight = hgt;
     }
 
-    addDocument = (doc: Doc) => {
+    addDocumentFilter = (doc: Doc) => {
         doc.presentationTargetDoc = doc.aliasOf;
-        return Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
+        return true;
     }
 
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
@@ -306,7 +306,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                         PanelHeight={this.panelHeight}
                         moveDocument={returnFalse}
                         childLayoutTemplate={this.childLayoutTemplate}
-                        addDocument={this.addDocument}
+                        filterAddDocument={this.addDocumentFilter}
                         removeDocument={returnFalse}
                         RenderData={this.returnSelf}
                         focus={this.selectElement}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 77eee03ce..71325d94f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -997,7 +997,7 @@ export namespace Doc {
         //newCollection.borderRounding = "40px";
         newCollection._jitterRotation = 10;
         newCollection._backgroundColor = "gray";
-        newCollection.overflow = "visible";
+        newCollection._overflow = "visible";
         return newCollection;
     }
 
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index cd4b9d591..7bf1c03c8 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -20,6 +20,7 @@ export const documentSchema = createSchema({
     _yPadding: "number",        // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set
     _xMargin: "number",         // margin added on left/right of most documents to add separation from their container
     _yMargin: "number",         // margin added on top/bottom of most documents to add separation from their container
+    _overflow: "string",        // sets overflow behvavior for CollectionFreeForm views
     _showCaption: "string",     // whether editable caption text is overlayed at the bottom of the document 
     _showTitle: "string",       // the fieldkey whose contents should be displayed at the top of the document
     _showTitleHover: "string",  // the showTitle should be shown only on hover
@@ -36,7 +37,6 @@ export const documentSchema = createSchema({
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
-    overflow: "string",         // sets overflow behvavior for CollectionFreeForm views
     creationDate: DateField,    // when the document was created
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
@@ -46,7 +46,7 @@ export const documentSchema = createSchema({
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field
-    isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
+    isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
     treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
     treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
     treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index f37538252..fbb27f9b7 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -89,7 +89,7 @@ export class CurrentUserUtils {
             const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" });
 
             const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true });
-            const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 50, _autoHeight: true });
+            const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true });
             const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true });
 
             const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"];
@@ -111,8 +111,9 @@ export class CurrentUserUtils {
 
             const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
             descriptionWrapper.sectionHeaders = new List<SchemaHeaderField>([
-                new SchemaHeaderField("[Long Description]", "LemonChiffon", undefined, undefined, undefined, true),
-                new SchemaHeaderField("[Details]", "lightBlue", undefined, undefined, undefined, true),
+                new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false),
+                new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true),
+                new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true),
             ]);
             const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts });
             detailView.isTemplateDoc = makeTemplate(detailView);
-- 
cgit v1.2.3-70-g09d2


From 40d5c3acab6dbdc67f6d4bfd15c802da9fe08ca0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 2 May 2020 00:17:21 -0400
Subject: cleaned up a lot of layoutTemplate/String props.  fixed link drawing.

---
 src/client/documents/Documents.ts                  |  7 +-
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/DocComponent.tsx                  |  4 +-
 src/client/views/DocumentDecorations.scss          | 24 +++---
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/MainView.scss                     |  6 +-
 .../views/collections/CollectionCarouselView.tsx   | 11 +--
 .../views/collections/CollectionStackingView.tsx   | 29 ++++---
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../views/collections/CollectionTimeView.tsx       |  4 +-
 .../views/collections/CollectionTreeView.tsx       |  8 +-
 src/client/views/collections/CollectionView.tsx    | 14 +++-
 .../views/collections/CollectionViewChromes.tsx    |  6 +-
 .../CollectionFreeFormLinkView.tsx                 |  2 +-
 .../CollectionFreeFormLinksView.tsx                |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 10 +--
 .../CollectionMulticolumnView.tsx                  |  5 +-
 .../CollectionMultirowView.tsx                     |  5 +-
 src/client/views/nodes/AudioBox.tsx                |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  4 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |  5 +-
 src/client/views/nodes/DocumentBox.tsx             | 94 ++++++++++++++-------
 src/client/views/nodes/DocumentContentsView.tsx    | 44 +++++-----
 src/client/views/nodes/DocumentView.tsx            | 29 +++----
 src/client/views/nodes/FieldView.tsx               |  3 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 -
 src/client/views/nodes/ScreenshotBox.tsx           |  6 +-
 src/client/views/nodes/VideoBox.tsx                |  6 +-
 src/new_fields/Doc.ts                              |  2 +-
 src/new_fields/documentSchemas.ts                  | 95 ++++++++++++----------
 .../authentication/models/current_user_utils.ts    |  2 +-
 32 files changed, 247 insertions(+), 195 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 434b26312..672f94f75 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -95,6 +95,7 @@ export interface DocumentOptions {
     forceActive?: boolean;
     layout?: string | Doc; // default layout string for a document
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
+    childLayoutString?: string; // template string for collection to use to render its children
     hideFilterView?: boolean; // whether to hide the filter popout on collections
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
@@ -123,7 +124,7 @@ export interface DocumentOptions {
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     borderRounding?: string;
     boxShadow?: string;
-    dontRegisterChildren?: boolean;
+    dontRegisterChildViews?: boolean;
     "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
     "onClick-rawScript"?: string; // onClick script in raw text form
     "onCheckedClick-rawScript"?: string; // onChecked script in raw text form
@@ -592,9 +593,7 @@ export namespace Docs {
             linkDocProto.anchor1_timecode = source.doc.currentTimecode || source.doc.displayTimecode;
             linkDocProto.anchor2_timecode = target.doc.currentTimecode || target.doc.displayTimecode;
 
-            if (linkDocProto.layout_key1 === undefined) {
-                Cast(linkDocProto.proto, Doc, null).layout_key1 = LinkAnchorBox.LayoutString("anchor1");
-                Cast(linkDocProto.proto, Doc, null).layout_key2 = LinkAnchorBox.LayoutString("anchor2");
+            if (linkDocProto.linkBoxExcludedKeys === undefined) {
                 Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
                 Cast(linkDocProto.proto, Doc, null).layoutKey = undefined;
             }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4683e77a8..1ba6f0248 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -117,7 +117,7 @@ export class DocumentManager {
                 pairs.push(...linksList.reduce((pairs, link) => {
                     const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
                     linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                        if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) {
+                        if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== docView1.props.LayoutTemplateString) {
                             pairs.push({ a: dv, b: docView1, l: link });
                         }
                     });
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 629b0f447..fd0d2bdbb 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -11,7 +11,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 ///  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>;
+    LayoutTemplate?: () => Opt<Doc>;
 }
 export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
@@ -20,7 +20,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the "The Document" -- it encapsulates, data, layout, and any templates
         @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
         // 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.props.LayoutDoc?.()); }
+        @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
         // 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] as Doc; }
 
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 61d517d43..4f34eb9e3 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -44,7 +44,6 @@ $linkGap : 3px;
 
     .documentDecorations-radius {
         pointer-events: auto;
-        background: black;
         opacity: 1;
         transform: translate(10px, 10px);
         grid-row: 4;
@@ -88,12 +87,12 @@ $linkGap : 3px;
         opacity: 1;
     }
     #documentDecorations-topLeftResizer {
-        border-left: black 2px solid;
-        border-top: black solid 2px;
+        border-left:  2px solid;
+        border-top:  solid 2px;
     }
     #documentDecorations-bottomRightResizer {
-        border-right: black 2px solid;
-        border-bottom: black solid 2px;
+        border-right:  2px solid;
+        border-bottom:  solid 2px;
     }
     #documentDecorations-topLeftResizer:hover,
     #documentDecorations-bottomRightResizer:hover {
@@ -111,12 +110,12 @@ $linkGap : 3px;
         opacity: 1;
     }
     #documentDecorations-topRightResizer {
-        border-right: black 2px solid;
-        border-top: black 2px solid;
+        border-right:  2px solid;
+        border-top:  2px solid;
     }
     #documentDecorations-bottomLeftResizer {
-        border-left: black 2px solid;
-        border-bottom: black 2px solid;
+        border-left:  2px solid;
+        border-bottom:  2px solid;
     }
     #documentDecorations-topRightResizer:hover,
     #documentDecorations-bottomLeftResizer:hover {
@@ -136,7 +135,6 @@ $linkGap : 3px;
     }
 
     .documentDecorations-contextMenu {
-        background: $alt-accent;
         width: 25px;
         height: calc(100% + 8px); // 8px for the height of the top resizer bar
         grid-column-start: 1;
@@ -144,14 +142,14 @@ $linkGap : 3px;
         pointer-events: all;
     }
     .documentDecorations-title {
-        background: $alt-accent;
         opacity: 1;
         grid-column-start: 3;
         grid-column-end: 4;
         pointer-events: auto;
         overflow: hidden;
         text-align: center;
-        display:flex;
+        display: flex;    
+        border-bottom: solid 1px;
     }
     .publishBox {
         width: 20px;
@@ -168,7 +166,6 @@ $linkGap : 3px;
 
 
 .documentDecorations-closeButton {
-    background: $alt-accent;
     opacity: 1;
     grid-column-start: 4;
     grid-column-end: 6;
@@ -178,7 +175,6 @@ $linkGap : 3px;
 }
 
 .documentDecorations-minimizeButton {
-    background: $alt-accent;
     opacity: 1;
     grid-column-start: 1;
     grid-column-end: 3;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e2759291a..396fe12b5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DataSym, Field } from "../../new_fields/Doc";
-import { PositionDocument } from '../../new_fields/documentSchemas';
+import { Document } from '../../new_fields/documentSchemas';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { Cast, StrCast, NumCast } from "../../new_fields/Types";
 import { Utils, setupMoveUpEvents, emptyFunction, returnFalse, simulateMouseClick } from "../../Utils";
@@ -301,7 +301,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = PositionDocument(element.rootDoc);
+                const doc = Document(element.rootDoc);
                 let nwidth = doc._nativeWidth || 0;
                 let nheight = doc._nativeHeight || 0;
                 const width = (doc._width || 0);
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 81d427f64..04288a9e1 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -39,7 +39,12 @@
     }
 }
 
+.mainView-container {
+    color:dimgray;
+}
+
 .mainView-container-dark {
+    color: lightgray;
     .lm_goldenlayout {
         background: dimgray;
     }
@@ -54,7 +59,6 @@
     }
     .contextMenu-cont, .contextMenu-item {
         background: dimGray;
-        color: lightgray;
     }
     .contextMenu-item:hover {
         background: gray;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 4086294ad..769b323ae 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -2,9 +2,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observable, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { documentSchema } from '../../../new_fields/documentSchemas';
+import { documentSchema, collectionSchema } from '../../../new_fields/documentSchemas';
 import { makeInterface } from '../../../new_fields/Schema';
-import { NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
+import { NumCast, StrCast, ScriptCast, Cast } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
 import "./CollectionCarouselView.scss";
@@ -16,8 +16,8 @@ import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import { returnFalse } from '../../../Utils';
 
-type CarouselDocument = makeInterface<[typeof documentSchema,]>;
-const CarouselDocument = makeInterface(documentSchema);
+type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const CarouselDocument = makeInterface(documentSchema, collectionSchema);
 
 @observer
 export class CollectionCarouselView extends CollectionSubView(CarouselDocument) {
@@ -40,7 +40,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
         e.stopPropagation();
         this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
     }
-
     panelHeight = () => this.props.PanelHeight() - 50;
     @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
@@ -51,6 +50,8 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                         onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
                         onClick={ScriptCast(this.layoutDoc.onChildClick)}
                         renderDepth={this.props.renderDepth + 1}
+                        LayoutTemplate={this.props.ChildLayoutTemplate}
+                        LayoutTemplateString={this.props.ChildLayoutString}
                         Document={this.childLayoutPairs[index].layout}
                         DataDoc={this.childLayoutPairs[index].data}
                         PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 01766f65f..eb70cec9d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,15 +4,17 @@ import { CursorProperty } from "csstype";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
+import { DataSym, Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
+import { listSpec, makeInterface } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DragManager, dropActionType } from "../../util/DragManager";
+import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
@@ -24,11 +26,14 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
+import { ScriptField } from "../../../new_fields/ScriptField";
 const _global = (window /* browser */ || global /* node */) as any;
 
+type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
+const StackingDocument = makeInterface(collectionSchema, documentSchema);
+
 @observer
-export class CollectionStackingView extends CollectionSubView(doc => doc) {
+export class CollectionStackingView extends CollectionSubView(StackingDocument) {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     _pivotFieldDisposer?: IReactionDisposer;
@@ -116,7 +121,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     getSimpleDocHeight(d?: Doc) {
         if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -160,14 +165,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         }
         return this.props.addDocTab(doc, where);
     }
+
     getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
-        const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(doc, this.props.ChildLayoutTemplate?.());
         const height = () => this.getDocHeight(doc);
         return <ContentFittingDocumentView
             Document={doc}
             DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
-            LayoutDoc={this.props.childLayoutTemplate}
+            LayoutTemplate={this.props.ChildLayoutTemplate}
+            LayoutTemplateString={this.props.ChildLayoutString}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
@@ -176,7 +183,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             PanelHeight={height}
             NativeHeight={returnZero}
             NativeWidth={returnZero}
-            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
@@ -199,13 +206,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     getDocWidth(d?: Doc) {
         if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         return Math.min(nw && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
-        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e44bbae78..aaea13ded 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -43,6 +43,8 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
+    ChildLayoutTemplate?: () => Doc;
+    ChildLayoutString?: string;
     childClickScript?: ScriptField;
     childDoubleClickScript?: ScriptField;
     freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 045134225..a2d4774c8 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -28,7 +28,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
     async componentDidMount() {
-        const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
         const childText = "const alias = getAlias(self); 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! });
@@ -84,7 +84,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         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} />
+            <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={true} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 296c1a39c..dc9348664 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -6,7 +6,6 @@ import { observer } from "mobx-react";
 import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
-import { RichTextField } from '../../../new_fields/RichTextField';
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
@@ -15,7 +14,6 @@ import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
-import { makeTemplate } from '../../util/DropConverter';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
@@ -481,7 +479,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)}
+                        dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)}
                         ContainingCollectionView={undefined}
                         ContainingCollectionDoc={this.props.containingCollection}
                     />}
@@ -743,11 +741,11 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
                             Doc.GetProto(img).doubleClickView = doubleClickView;
                         }
                     });
-                    d.layout = ImageBox.LayoutString("hero");
+                    Doc.GetProto(d).layout = ImageBox.LayoutString("hero");
                 });
 
                 Document.childLayoutTemplate = heroView;
-                Document.childDetailView = detailView;
+                Document.childClickedOpenTemplateView = 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 91018980c..d2afb4855 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -72,7 +72,8 @@ export enum CollectionViewType {
     Pile = "pileup"
 }
 export interface CollectionViewCustomProps {
-    filterAddDocument: (doc: Doc) => boolean;
+    filterAddDocument: (doc: Doc) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+    childLayoutTemplate?: () => Opt<Doc>;  // specify a layout Doc template to use for children of the collection
 }
 
 export interface CollectionRenderProps {
@@ -82,6 +83,8 @@ export interface CollectionRenderProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     PanelWidth: () => number;
+    ChildLayoutTemplate?: () => Doc;
+    ChildLayoutString?: string;
 }
 
 @observer
@@ -245,8 +248,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             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.childDetailView instanceof Doc) {
-                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailView as Doc, "onRight"), icon: "project-diagram" });
+            if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
+                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView 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" });
 
@@ -474,6 +477,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 </div>
             </div>;
     }
+    childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
 
     render() {
         TraceMobx();
@@ -483,7 +487,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             moveDocument: this.moveDocument,
             active: this.active,
             whenActiveChanged: this.whenActiveChanged,
-            PanelWidth: this.bodyPanelWidth
+            PanelWidth: this.bodyPanelWidth,
+            ChildLayoutTemplate: this.childLayoutTemplate,
+            ChildLayoutString: StrCast(this.props.Document.childLayoutString),
         };
         return (<div className={"collectionView"}
             style={{
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index d26e3a38b..62b03bbdc 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -44,9 +44,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         initialize: emptyFunction,
     };
     _narrativeCommand = {
-        params: ["target", "source"], title: "=> click item view",
-        script: "this.target.childDetailView = getDocTemplate(this.source?.[0])",
-        immediate: (source: Doc[]) => this.target.childDetailView = Doc.getDocTemplate(source?.[0]),
+        params: ["target", "source"], title: "=> click clicked open view",
+        script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+        immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
         initialize: emptyFunction,
     };
     _contentCommand = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index cf12ef382..d67d1993e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -40,7 +40,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 const bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height,
                     abounds.left, abounds.top, abounds.width, abounds.height,
                     apt.point.x, apt.point.y);
-                const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
+                const afield = this.props.A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
                 const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
 
                 // really hacky stuff to make the LinkAnchorBox display where we want it to:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 4b5e977df..7df1e909f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -30,7 +30,7 @@ export class CollectionFreeFormLinksView extends React.Component {
             return drawnPairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]);
         return connections.filter(c =>
-            c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK &&
+            c.a.props.Document.type === DocumentType.LINK &&
             c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed
         ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 45ef0455e..707e103fb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
-import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
+import { documentSchema, collectionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkData, InkField, InkTool, PointData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
@@ -66,8 +66,8 @@ export const panZoomSchema = createSchema({
     fitH: "number"
 });
 
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
-const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
 export type collectionFreeformViewProps = {
     forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
@@ -858,7 +858,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
     parentActive = () => this.props.active() || this.backgroundActive ? true : false;
-    childLayoutFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
@@ -868,7 +867,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
-            LayoutDoc: this.childLayoutFunc,
+            LayoutTemplate: this.props.ChildLayoutTemplate,
+            LayoutTemplateString: this.props.ChildLayoutString,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
             setupDragLines: this.setupDragLines,
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b3a6a9deb..38b16e184 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -218,7 +218,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             Document={layout}
             DataDoc={layout.resolvedDataDoc as Doc}
             backgroundColor={this.props.backgroundColor}
-            LayoutDoc={this.props.childLayoutTemplate}
+            LayoutTemplate={this.props.ChildLayoutTemplate}
+            LayoutTemplateString={this.props.ChildLayoutString}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
@@ -226,7 +227,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             PanelHeight={height}
             NativeHeight={returnZero}
             NativeWidth={returnZero}
-            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 0fb29ca61..b55b67a9e 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -217,7 +217,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             Document={layout}
             DataDoc={layout.resolvedDataDoc as Doc}
             backgroundColor={this.props.backgroundColor}
-            LayoutDoc={this.props.childLayoutTemplate}
+            LayoutTemplate={this.props.ChildLayoutTemplate}
+            LayoutTemplateString={this.props.ChildLayoutString}
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
@@ -225,7 +226,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             PanelHeight={height}
             NativeHeight={returnZero}
             NativeWidth={returnZero}
-            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            fitToBox={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6ff6d1b42..3feb533a0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -21,6 +21,7 @@ import { Docs } from "../../documents/Documents";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Networking } from "../../Network";
 import { Upload } from "../../../server/SharedMediaTypes";
+import { LinkAnchorBox } from "./LinkAnchorBox";
 
 // testing testing 
 
@@ -266,7 +267,8 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
                                                 NativeHeight={returnZero}
                                                 NativeWidth={returnZero}
                                                 rootSelected={returnFalse}
-                                                layoutKey={Doc.LinkEndpoint(l, la2)}
+                                                LayoutTemplate={undefined}
+                                                LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
                                                 ContainingCollectionDoc={this.props.Document}
                                                 parentActive={returnTrue}
                                                 bringToFront={emptyFunction}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 24468dcc1..2894d9b65 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -7,7 +7,7 @@ import { DocComponent } from "../DocComponent";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
-import { PositionDocument } from "../../../new_fields/documentSchemas";
+import { Document } from "../../../new_fields/documentSchemas";
 import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
@@ -28,7 +28,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, Document>(Document) {
     @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);
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 637fd5acc..3c2c6c87e 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -14,7 +14,7 @@ import "./ContentFittingDocumentView.scss";
 @observer
 export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
-    private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
+    private get layoutDoc() { return this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document); }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
     nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
@@ -56,7 +56,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
                     <DocumentView {...this.props}
                         Document={this.props.Document}
                         DataDoc={this.props.DataDoc}
-                        LayoutDoc={this.props.LayoutDoc}
+                        LayoutTemplate={this.props.LayoutTemplate}
+                        LayoutTemplateString={this.props.LayoutTemplateString}
                         LibraryPath={this.props.LibraryPath}
                         NativeWidth={this.nativeWidth}
                         NativeHeight={this.nativeHeight}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 88eeb1137..8d422fe67 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IReactionDisposer, reaction, computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, Field } from "../../../new_fields/Doc";
-import { documentSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema, collectionSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -16,9 +16,11 @@ import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
 import { TraceMobx } from "../../../new_fields/util";
 import { Docs } from "../../documents/Documents";
+import { KeyValueBox } from "./KeyValueBox";
+import { DocumentView } from "./DocumentView";
 
-type DocHolderBoxSchema = makeInterface<[typeof documentSchema]>;
-const DocHolderBoxDocument = makeInterface(documentSchema);
+type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
+const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
 
 @observer
 export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) {
@@ -43,7 +45,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
         funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" });
         funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" });
-        funcs.push({ description: `Show ${this.layoutDoc.childTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childTemplateName = this.layoutDoc.childTemplateName ? undefined : "keyValue", icon: "project-diagram" });
+        funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "<KeyValueBox {...props} />", icon: "project-diagram" });
 
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
@@ -103,6 +105,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
     pwidth = () => this.props.PanelWidth() - 2 * this.xPad;
     pheight = () => this.props.PanelHeight() - 2 * this.yPad;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad);
+    isActive = () => this.active() || !this.props.renderDepth;
     get renderContents() {
         const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null);
         const childTemplateName = StrCast(this.layoutDoc.childTemplateName);
@@ -112,33 +115,62 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                 Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
             }, 0);
         }
-        const contents = !(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView
-            Document={containedDoc}
-            DataDoc={undefined}
-            LibraryPath={emptyPath}
-            ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
-            ContainingCollectionDoc={undefined}
-            fitToBox={true}
-            layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
-            rootSelected={this.props.isSelected}
-            addDocument={this.props.addDocument}
-            moveDocument={this.props.moveDocument}
-            removeDocument={this.props.removeDocument}
-            addDocTab={this.props.addDocTab}
-            pinToPres={this.props.pinToPres}
-            ScreenToLocalTransform={this.getTransform}
-            renderDepth={this.props.renderDepth + 1}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
-            PanelWidth={this.pwidth}
-            PanelHeight={this.pheight}
-            focus={this.props.focus}
-            parentActive={this.props.active}
-            dontRegisterView={!this.isSelectionLocked()}
-            whenActiveChanged={this.props.whenActiveChanged}
-            bringToFront={returnFalse}
-            ContentScaling={returnOne}
-        />;
+        const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString ?
+            <DocumentView
+                Document={containedDoc}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
+                ContainingCollectionDoc={undefined}
+                fitToBox={true}
+                LayoutTemplateString={StrCast(this.layoutDoc.childLayoutString)}
+                //layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
+                rootSelected={this.props.isSelected}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                ScreenToLocalTransform={this.getTransform}
+                renderDepth={this.props.renderDepth + 1}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                PanelWidth={this.pwidth}
+                PanelHeight={this.pheight}
+                focus={this.props.focus}
+                parentActive={this.isActive}
+                dontRegisterView={!this.isSelectionLocked()}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne} /> :
+            <ContentFittingDocumentView
+                Document={containedDoc}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
+                ContainingCollectionDoc={undefined}
+                fitToBox={true}
+                LayoutTemplateString={StrCast(this.layoutDoc.childLayoutString)}
+                //layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
+                rootSelected={this.props.isSelected}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                ScreenToLocalTransform={this.getTransform}
+                renderDepth={this.props.renderDepth + 1}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                PanelWidth={this.pwidth}
+                PanelHeight={this.pheight}
+                focus={this.props.focus}
+                parentActive={this.isActive}
+                dontRegisterView={!this.isSelectionLocked()}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne}
+            />;
         return contents;
     }
     render() {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4d20d3e2c..749fb98be 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -96,8 +96,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: (outsideReaction: boolean) => boolean,
     select: (ctrl: boolean) => void,
     layoutKey: string,
-    forceLayout?: string,
-    forceFieldKey?: string,
     hideOnLeave?: boolean,
     makeLink?: () => Opt<Doc>,  // function to call when a link is made
 }> {
@@ -105,6 +103,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         TraceMobx();
         if (!this.layoutDoc) return "<p>awaiting layout</p>";
         // const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");  // bcz: replaced this with below... is it right?
+        if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
         const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string");
         if (this.props.layoutKey === "layout_keyValue") {
             return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
@@ -127,8 +126,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     get layoutDoc() {
         const params = StrCast(this.props.Document.PARAMS);
         // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
-        // const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
-        const template: Doc = this.props.LayoutDoc?.() ||
+        // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
+        const template: Doc = this.props.LayoutTemplate?.() || (this.props.LayoutTemplateString && this.props.Document) ||
             (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
             Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
         return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
@@ -186,25 +185,22 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
 
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
-            this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
-                <FormattedTextBox {...bindings.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, HTMLtag
-                    }}
-                    bindings={bindings}
-                    jsx={layoutFrame}
-                    showWarnings={true}
-
-                    onError={(test: any) => { console.log(test); }}
-                />;
+            <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, HTMLtag
+                }}
+                bindings={bindings}
+                jsx={layoutFrame}
+                showWarnings={true}
+
+                onError={(test: any) => { console.log(test); }}
+            />;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f1efa48f4..7c7c03db2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,22 +5,21 @@ import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
+import { Document } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
-import { RichTextField } from '../../../new_fields/RichTextField';
 import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField';
 import { ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField';
+import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { ClientRecommender } from '../../ClientRecommender';
 import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DocumentManager } from "../../util/DocumentManager";
@@ -42,6 +41,7 @@ import { InkingControl } from '../InkingControl';
 import { KeyphraseQueryView } from '../KeyphraseQueryView';
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
+import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
 
@@ -58,7 +58,8 @@ export interface DocumentViewProps {
     NativeHeight: () => number;
     Document: Doc;
     DataDoc?: Doc;
-    LayoutDoc?: () => Opt<Doc>;
+    LayoutTemplateString?: string;
+    LayoutTemplate?: () => Opt<Doc>;
     LibraryPath: Doc[];
     fitToBox?: boolean;
     contextMenuItems?: () => { script: ScriptField, label: string }[];
@@ -454,8 +455,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const dY = -1 * Math.sign(dH);
 
             if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = PositionDocument(this.props.Document);
-                const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
+                const doc = Document(this.props.Document);
+                const layoutDoc = Document(Doc.Layout(this.props.Document));
                 let nwidth = layoutDoc._nativeWidth || 0;
                 let nheight = layoutDoc._nativeHeight || 0;
                 const width = (layoutDoc._width || 0);
@@ -984,13 +985,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get contents() {
         TraceMobx();
         return (<>
-            <DocumentContentsView key={1} ContainingCollectionView={this.props.ContainingCollectionView}
+            <DocumentContentsView key={1}
+                ContainingCollectionView={this.props.ContainingCollectionView}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 NativeWidth={this.NativeWidth}
                 NativeHeight={this.NativeHeight}
                 Document={this.props.Document}
                 DataDoc={this.props.DataDoc}
-                LayoutDoc={this.props.LayoutDoc}
+                LayoutTemplateString={this.props.LayoutTemplateString}
+                LayoutTemplate={this.props.LayoutTemplate}
                 makeLink={this.makeLink}
                 rootSelected={this.rootSelected}
                 RenderData={this.props.RenderData}
@@ -1021,7 +1024,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         </>
         );
     }
-    linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
 
     // used to decide whether a link anchor view should be created or not.
     // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
@@ -1049,12 +1051,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
                 PanelWidth={this.anchorPanelWidth}
                 PanelHeight={this.anchorPanelHeight}
-                layoutKey={this.linkEndpoint(d)}
                 ContentScaling={returnOne}
                 backgroundColor={returnTransparent}
                 removeDocument={this.hideLinkAnchor}
                 pointerEvents={false}
-                LayoutDoc={undefined}
+                LayoutTemplate={undefined}
+                LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)}
             />);
     }
     @computed get innards() {
@@ -1073,8 +1075,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
                 <DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
                     hideOnLeave={true}
-                    forceLayout={"FormattedTextBox"}
-                    forceFieldKey={showCaption}
+                    layoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`}
                     ContentScaling={this.childScaling}
                     ChromeHeight={this.chromeHeight}
                     isSelected={this.isSelected}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1efee4f5a..40d55ce38 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -50,13 +50,12 @@ export interface FieldViewProps {
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
     ChromeHeight?: () => number;
-    childLayoutTemplate?: () => Opt<Doc>;
+    RenderData?: () => Doc;
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     height?: number;
     width?: number;
     background?: string;
     color?: string;
-    RenderData?: () => Doc;
 }
 
 @observer
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2970674a2..d43936949 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -36,7 +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 ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
+    get fieldDocToLayout() { return this.props.Document; }
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 6c50abf21..bc36e056e 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -17,7 +17,6 @@ import { LinkEditor } from "../linking/LinkEditor";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../new_fields/util";
-import { DocumentView } from "./DocumentView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 125690dc7..a0ecc9ff5 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
-import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
@@ -20,8 +20,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
 const path = require('path');
 
-type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
+type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
+const ScreenshotDocument = makeInterface(documentSchema);
 
 library.add(faVideo);
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 613929bca..266b7f43f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -21,14 +21,14 @@ import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./VideoBox.scss";
-import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 const path = require('path');
 
 export const timeSchema = createSchema({
     currentTimecode: "number",  // the current time of a video or other linear, time-based document.  Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first
 });
-type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
-const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
+type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>;
+const VideoDocument = makeInterface(documentSchema, timeSchema);
 
 library.add(faVideo);
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 71325d94f..9256f82c2 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -772,7 +772,7 @@ export namespace Doc {
 
 
     export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; }
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layout_key1" : "layout_key2"; }
+    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; }
 
     export function linkFollowUnhighlight() {
         Doc.UnhighlightAll();
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index fd9a304f9..e71fc27f3 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -4,13 +4,25 @@ import { Doc } from "./Doc";
 import { DateField } from "./DateField";
 
 export const documentSchema = createSchema({
+    // content properties
     type: "string",             // enumerated type of document -- should be template-specific (ie, start with an '_')
-    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
-    layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
     title: "string",            // document title (can be on either data document or layout)
-    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
-    childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field
+    creationDate: DateField,    // when the document was created
+    links: listSpec(Doc),       // computed (readonly) list of links associated with this document
+
+    // "Location" properties in a very general sense
+    currentTimecode: "number",  // current play back time of a temporal document (video / audio)
+    displayTimecode: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
+    x: "number",                // x coordinate when in a freeform view 
+    y: "number",                // y coordinate when in a freeform view 
+    z: "number",                // z "coordinate" - non-zero specifies the overlay layer of a freeformview
+    zIndex: "number",           // zIndex of a document in a freeform view
+    scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+    scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
+
+    // appearance properties on the layout
     _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
     _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
     _nativeHeight: "number",    // "
@@ -27,59 +39,57 @@ export const documentSchema = createSchema({
     _showAudio: "boolean",      // whether to show the audio record icon on documents
     _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
     _LODdisable: "boolean",     // whether to disbale LOD switching for CollectionFreeFormViews
-    _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
+    _pivotField: "string",      // specifies which field key should be used as the timeline/pivot axis
     _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
     _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
-    _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
     _fontSize: "number",
     _fontFamily: "string",
-    isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
-    color: "string",            // foreground color of document
+
+    // appearance properties on the data document
     backgroundColor: "string",  // background color of document
+    borderRounding: "string",   // border radius rounding of document
+    boxShadow: "string",        // the amount of shadow around the perimeter of a document
+    color: "string",            // foreground color of document
+    fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
+    fontSize: "string",
+    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
+    layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
+    letterSpacing: "string",
     opacity: "number",          // opacity of document
-    creationDate: DateField,    // when the document was created
-    links: listSpec(Doc),       // computed (readonly) list of links associated with this document
+    strokeWidth: "number",
+    textTransform: "string",
+    treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
+    treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
+    treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+
+    // interaction and linking properties
+    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
-    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
-    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
-    isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field
-    isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
-    treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
-    treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
-    treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
-    currentTimecode: "number",  // current play back time of a temporal document (video / audio)
     followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) 
+    isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
+    isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
+    isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
     lockedTransform: "boolean", // whether the document can be panned/zoomed
-    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
-    borderRounding: "string",   // border radius rounding of document
-    heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
-    isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
-    ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
-    scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
-    scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
-    scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
-    strokeWidth: "number",
-    fontSize: "string",
-    fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
-    letterSpacing: "string",
-    textTransform: "string",
-    childTemplateName: "string" // the name of a template to use to override the layoutKey when rendering a document in DocHolderBox 
-});
 
-export const positionSchema = createSchema({
-    zIndex: "number",
-    x: "number",
-    y: "number",
-    z: "number",
+    // drag drop properties
+    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
+    dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
+    childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
 });
 
+
 export const collectionSchema = createSchema({
-    childLayoutTemplate: Doc, // layout template for children of a collecion
-    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)
+    childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox 
+    childLayoutTemplate: Doc, // layout template to use to render children of a collecion
+    childLayoutString: "string", //layout string to use to render children of a collection
+    childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
+    dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
     onChildClick: ScriptField, // script to run for each child when its clicked
     onChildDoubleClick: 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
@@ -87,6 +97,3 @@ export const collectionSchema = createSchema({
 
 export type Document = makeInterface<[typeof documentSchema]>;
 export const Document = makeInterface(documentSchema);
-
-export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
-export const PositionDocument = makeInterface(documentSchema, positionSchema);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 2ae42bf52..b9de93559 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -485,7 +485,7 @@ export class CurrentUserUtils {
                 _width: 50, _height: 25, title: "Library", _fontSize: 10,
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
-                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
+                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
                 })) as any as Doc,
                 targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
                 onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
-- 
cgit v1.2.3-70-g09d2


From 100ad0da00f2a5cea13508abc0c3a8c644095d65 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 2 May 2020 14:31:16 -0400
Subject: turn off targetDropAction when dropping in same colleciton.  cleaned
 up PresBox stuff to use single template to render all contents (which are
 otherwise unmodified).

---
 src/client/util/DragManager.ts                     |  4 +-
 .../views/collections/CollectionCarouselView.tsx   |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  1 -
 src/client/views/collections/CollectionSubView.tsx | 11 +++-
 src/client/views/collections/CollectionView.tsx    |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 src/client/views/nodes/DocumentView.tsx            |  2 -
 src/client/views/nodes/FieldView.tsx               |  3 +-
 src/client/views/nodes/PresBox.tsx                 | 72 ++++++++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  6 +-
 .../views/presentationview/PresElementBox.tsx      | 16 ++---
 src/new_fields/documentSchemas.ts                  |  2 +-
 .../authentication/models/current_user_utils.ts    |  7 +--
 13 files changed, 71 insertions(+), 60 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c06ad3d60..041f2fc2c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -185,7 +185,8 @@ export namespace DragManager {
     export function MakeDropTarget(
         element: HTMLElement,
         dropFunc: (e: Event, de: DropEvent) => void,
-        doc?: Doc
+        doc?: Doc,
+        preDropFunc?: (e: Event, de: DropEvent) => void,
     ): DragDropDisposer {
         if ("canDrop" in element.dataset) {
             throw new Error(
@@ -199,6 +200,7 @@ export namespace DragManager {
             if (de.complete.docDragData && doc?.targetDropAction) {
                 de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
             }
+            preDropFunc?.(e, de);
         };
         element.addEventListener("dashOnDrop", handler);
         doc && element.addEventListener("dashPreDrop", preDropHandler);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 769b323ae..a04136e51 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -69,7 +69,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                     <FormattedTextBox key={index} {...this.props}
                         xMargin={NumCast(this.layoutDoc["_carousel-caption-xMargin"])}
                         yMargin={NumCast(this.layoutDoc["_carousel-caption-yMargin"])}
-                        Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+                        Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"} />
                 </div>
             </>;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index eb70cec9d..1fd5c3f44 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -178,7 +178,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             LibraryPath={this.props.LibraryPath}
             FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
-            RenderData={this.props.RenderData}
             PanelWidth={width}
             PanelHeight={height}
             NativeHeight={returnZero}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index aaea13ded..af642bc52 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -67,7 +67,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             this.multiTouchDisposer?.();
             if (ele) {
                 this._mainCont = ele;
-                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
@@ -195,6 +195,15 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
         }
 
+        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) {
+            if (de.complete.docDragData) {
+                if (de.complete.docDragData.draggedDocuments.some(d => this.childDocs.includes(d))) {
+                    de.complete.docDragData.dropAction = "move";
+                }
+                e.stopPropagation();
+            }
+        }
+
         @undoBatch
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d2afb4855..cb7d86e00 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -74,6 +74,7 @@ export enum CollectionViewType {
 export interface CollectionViewCustomProps {
     filterAddDocument: (doc: Doc) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     childLayoutTemplate?: () => Opt<Doc>;  // specify a layout Doc template to use for children of the collection
+    childLayoutString?: string;  // specify a layout string to use for children of the collection
 }
 
 export interface CollectionRenderProps {
@@ -478,6 +479,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             </div>;
     }
     childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
+    childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString);
 
     render() {
         TraceMobx();
@@ -489,7 +491,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             whenActiveChanged: this.whenActiveChanged,
             PanelWidth: this.bodyPanelWidth,
             ChildLayoutTemplate: this.childLayoutTemplate,
-            ChildLayoutString: StrCast(this.props.Document.childLayoutString),
+            ChildLayoutString: this.childLayoutString,
         };
         return (<div className={"collectionView"}
             style={{
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 707e103fb..411fbd9e2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -878,7 +878,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onDoubleClick: this.onChildDoubleClickHandler,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
-            RenderData: this.props.RenderData,
             PanelWidth: childLayout[WidthSym],
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7c7c03db2..7a16e8836 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -78,7 +78,6 @@ export interface DocumentViewProps {
     setupDragLines?: () => void;
     renderDepth: number;
     ContentScaling: () => number;
-    RenderData?: () => Doc;
     PanelWidth: () => number;
     PanelHeight: () => number;
     pointerEvents?: boolean;
@@ -996,7 +995,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 LayoutTemplate={this.props.LayoutTemplate}
                 makeLink={this.makeLink}
                 rootSelected={this.rootSelected}
-                RenderData={this.props.RenderData}
                 dontRegisterView={this.props.dontRegisterView}
                 fitToBox={this.props.fitToBox}
                 LibraryPath={this.props.LibraryPath}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 40d55ce38..016d2a1ae 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -50,12 +50,13 @@ export interface FieldViewProps {
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
     ChromeHeight?: () => number;
-    RenderData?: () => Doc;
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     height?: number;
     width?: number;
     background?: string;
     color?: string;
+    xMargin?: number;
+    yMargin?: number;
 }
 
 @observer
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 6e3420f22..53b6aa408 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -17,6 +17,8 @@ import "./PresBox.scss";
 import { ViewBoxBaseComponent } from "../DocComponent";
 import { makeInterface } from "../../../new_fields/Schema";
 import { List } from "../../../new_fields/List";
+import { Docs } from "../../documents/Documents";
+import { PrefetchProxy } from "../../../new_fields/Proxy";
 
 type PresBoxSchema = makeInterface<[typeof documentSchema]>;
 const PresBoxDocument = makeInterface(documentSchema);
@@ -24,14 +26,32 @@ const PresBoxDocument = makeInterface(documentSchema);
 @observer
 export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+    _docsDisposer: IReactionDisposer | undefined;
+    _viewDisposer: IReactionDisposer | undefined;
     @observable _isChildActive = false;
     @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
-    @computed get currentIndex() { return NumCast(this.rootDoc._itemIndex); }
+    @computed get currentIndex() { return NumCast(this.presElement?.currentIndex); }
+    @computed get presElement() { return Cast(this.rootDoc.presElement, Doc, null); }
+    constructor(props: any) {
+        super(props);
+        if (!this.presElement) {
+            this.rootDoc.presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
+                title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data"
+            }));
+        }
+    }
+
+    componentWillUnmount() {
+        this._docsDisposer?.();
+        this._viewDisposer?.();
+    }
 
     componentDidMount() {
         this.rootDoc.presBox = this.rootDoc;
         this.rootDoc._forceRenderEngine = "timeline";
         this.rootDoc._replacedChrome = "replaced";
+        this._docsDisposer = reaction(() => this.childDocs, docs => this.presElement.presOrderedDocs = new List<Doc>(docs), { fireImmediately: true });
+        this._viewDisposer = reaction(() => this.rootDoc._viewType, viewType => this.presElement.presCollapsedHeight = viewType === CollectionViewType.Tree ? 50 : 46, { fireImmediately: true });
     }
     updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc;
 
@@ -166,17 +186,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         }
     }
 
-
     //The function that is called when a document is clicked or reached through next or back.
     //it'll also execute the necessary actions if presentation is playing.
     public gotoDocument = (index: number, fromDoc: number) => {
         this.updateCurrentPresentation();
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
-            this.rootDoc._itemIndex = index;
+            this.presElement.currentIndex = index;
 
-            if (!this.layoutDoc.presStatus) {
-                this.layoutDoc.presStatus = true;
+            if (!this.presElement.presStatus) {
+                this.presElement.presStatus = true;
                 this.startPresentation(index);
             }
 
@@ -189,10 +208,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     //The function that starts or resets presentaton functionally, depending on status flag.
     startOrResetPres = () => {
         this.updateCurrentPresentation();
-        if (this.layoutDoc.presStatus) {
+        if (this.presElement.presStatus) {
             this.resetPresentation();
         } else {
-            this.layoutDoc.presStatus = true;
+            this.presElement.presStatus = true;
             this.startPresentation(0);
             this.gotoDocument(0, this.currentIndex);
         }
@@ -204,7 +223,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         this.updateCurrentPresentation();
         this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1);
         this.rootDoc._itemIndex = 0;
-        this.layoutDoc.presStatus = false;
+        this.presElement.presStatus = false;
     }
 
     //The function that starts the presentation, also checking if actions should be applied
@@ -241,43 +260,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         }
     });
 
-    initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => {
-        const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
-        this.rootDoc.presCollapsedHeight = hgt;
-    }
+    @undoBatch
+    viewChanged = action((e: React.ChangeEvent) => {
+        //@ts-ignore
+        const viewType = e.target.selectedOptions[0].value as CollectionViewType;
+        viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
+        this.updateMinimize(e, this.rootDoc._viewType = viewType);
+    });
 
+    whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
     addDocumentFilter = (doc: Doc) => {
-        doc.presentationTargetDoc = doc.aliasOf;
+        doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
+        !this.childDocs.includes(doc) && (doc.presZoomButton = true);
         return true;
     }
-
+    childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
-
     selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.rootDoc._itemIndex));
-
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
-
     panelHeight = () => this.props.PanelHeight() - 20;
-
     active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
         (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
 
-    whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
-
-    @undoBatch
-    viewChanged = action((e: React.ChangeEvent) => {
-        //@ts-ignore
-        const viewType = e.target.selectedOptions[0].value as CollectionViewType;
-        viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
-        this.updateMinimize(e, this.rootDoc._viewType = viewType);
-    });
-
-    returnSelf = () => this.rootDoc;
-    childLayoutTemplate = () => this.rootDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined;
     render() {
         this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.map((child, i) => child));
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
-        this.initializeViewAliases(this.childDocs, mode);
         return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
             <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
                 <select className="presBox-viewPicker"
@@ -308,7 +315,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                         childLayoutTemplate={this.childLayoutTemplate}
                         filterAddDocument={this.addDocumentFilter}
                         removeDocument={returnFalse}
-                        RenderData={this.returnSelf}
                         focus={this.selectElement}
                         ScreenToLocalTransform={this.getTransform} />
                     : (null)
@@ -316,4 +322,4 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             </div>
         </div>;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3fb3f5644..4a75816a8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -63,9 +63,9 @@ library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
 
 export interface FormattedTextBoxProps {
-    hideOnLeave?: boolean;
-    makeLink?: () => Opt<Doc>;
-    xMargin?: number;
+    makeLink?: () => Opt<Doc>;  // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text
+    hideOnLeave?: boolean;  // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
+    xMargin?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView
     yMargin?: number;
 }
 
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 1887c8d45..fd202cdc1 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -5,7 +5,7 @@ import { Doc, DataSym, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from "../../../new_fields/FieldSymbols";
 import { createSchema, makeInterface } from '../../../new_fields/Schema';
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Cast, NumCast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionView';
@@ -38,11 +38,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
 
     _heightDisposer: IReactionDisposer | undefined;
-    @computed get indexInPres() { return DocListCast(this.presBoxDoc.presOrderedDocs).findIndex(d => d === this.rootDoc); }
-    @computed get presBoxDoc() { return Cast(this.props.RenderData?.().presBox, Doc) as Doc; }
+    @computed get indexInPres() { return DocListCast(this.layoutDoc.presOrderedDocs).findIndex(d => d === this.rootDoc); }
+    @computed get collapsedHeight() { return NumCast(this.layoutDoc.presCollapsedHeight); }
+    @computed get presStatus() { return BoolCast(this.layoutDoc.presStatus); }
+    @computed get currentIndex() { return NumCast(this.layoutDoc.currentIndex); }
     @computed get targetDoc() { return this.rootDoc.presentationTargetDoc as Doc; }
-    @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); }
-    @computed get collapsedHeight() { return NumCast(this.presBoxDoc?.presCollapsedHeight); }
 
     componentDidMount() {
         this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
@@ -65,7 +65,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                 this.targetDoc.opacity = 1;
             }
         } else {
-            if (this.presBoxDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) {
+            if (this.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) {
                 this.targetDoc.opacity = 0;
             }
         }
@@ -86,7 +86,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
             }
         } else {
             if (this.rootDoc.presFadeButton) this.rootDoc.presFadeButton = false;
-            if (this.presBoxDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) {
+            if (this.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) {
                 this.targetDoc.opacity = 0;
             }
         }
@@ -107,7 +107,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
             }
         } else {
             this.rootDoc.presHideAfterButton = false;
-            if (this.presBoxDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) {
+            if (this.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) {
                 this.targetDoc.opacity = 0.5;
             }
         }
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index e71fc27f3..e7d27d81e 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -78,7 +78,7 @@ export const documentSchema = createSchema({
     // drag drop properties
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
+    targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
     childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
 });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b9de93559..7f0fd5a8d 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -257,7 +257,7 @@ export class CurrentUserUtils {
     }[] {
         if (doc.emptyPresentation === undefined) {
             doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
-                { title: "Presentation", _viewType: CollectionViewType.Stacking, _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
+                { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
         }
         if (doc.emptyCollection === undefined) {
             doc.emptyCollection = Docs.Create.FreeformDocument([],
@@ -576,11 +576,6 @@ export class CurrentUserUtils {
 
     // the initial presentation Doc to use
     static setupDefaultPresentation(doc: Doc) {
-        if (doc["template-presentation"] === undefined) {
-            doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
-                title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data"
-            }));
-        }
         if (doc.activePresentation === undefined) {
             doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), {
                 title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias",
-- 
cgit v1.2.3-70-g09d2


From 36f7b31d98a561f6c4c71934ea46e43aa3afe317 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 3 May 2020 00:00:41 -0400
Subject: got rid of div from CollectionView.

---
 package-lock.json                               | 16 ++++++++++++++++
 package.json                                    |  2 +-
 src/client/views/collections/CollectionView.tsx |  6 +++---
 3 files changed, 20 insertions(+), 4 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 959fc41ef..9ac02f404 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2868,12 +2868,22 @@
                 "upath": "^1.1.1"
             },
             "dependencies": {
+                "bindings": {
+                    "version": "1.5.0",
+                    "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+                    "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+                    "optional": true,
+                    "requires": {
+                        "file-uri-to-path": "1.0.0"
+                    }
+                },
                 "fsevents": {
                     "version": "1.2.12",
                     "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz",
                     "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==",
                     "optional": true,
                     "requires": {
+                        "bindings": "^1.5.0",
                         "nan": "^2.12.1",
                         "node-pre-gyp": "*"
                     },
@@ -5663,6 +5673,12 @@
             "resolved": "https://registry.npmjs.org/file-type/-/file-type-7.7.1.tgz",
             "integrity": "sha512-bTrKkzzZI6wH+NXhyD3SOXtb2zXTw2SbwI2RxUlRcXVsnN7jNL5hJzVQLYv7FOQhxFkK4XWdAflEaWFpaLLWpQ=="
         },
+        "file-uri-to-path": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+            "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+            "optional": true
+        },
         "filewatcher": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/filewatcher/-/filewatcher-3.0.1.tgz",
diff --git a/package.json b/package.json
index 84c23147a..ef30a3385 100644
--- a/package.json
+++ b/package.json
@@ -164,7 +164,7 @@
         "formidable": "^1.2.1",
         "golden-layout": "^1.5.9",
         "google-auth-library": "^4.2.4",
-        "google-maps-react": "^2.0.2",
+        "google-maps-react": "^2.0.6",
         "googleapis": "^40.0.0",
         "googlephotos": "^0.2.5",
         "howler": "^2.1.3",
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index cb7d86e00..43da1c44f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -201,8 +201,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
         const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
-            <CollectionViewBaseChrome CollectionView={this} key="chrome" PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
-        return [chrome, this.SubViewHelper(type, renderProps)];
+            <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
+        return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
     }
 
 
@@ -501,7 +501,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             }}
             onContextMenu={this.onContextMenu}>
             {this.showIsTagged()}
-            <div style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
+            <div className="collectionView-facetCont" 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 =>
-- 
cgit v1.2.3-70-g09d2


From 34db0a78d2dc8989313decf8993691f40847b231 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 7 May 2020 18:13:16 -0400
Subject: fixes for adding/remove documents -- needs to be extended for views
 other than freeform.

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    |  23 +++--
 src/client/views/collections/CollectionSubView.tsx |  26 +++---
 src/client/views/collections/CollectionView.scss   |   2 +-
 src/client/views/collections/CollectionView.tsx    |  54 ++++++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 101 +++++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |  13 +--
 6 files changed, 123 insertions(+), 96 deletions(-)

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index ff471853a..1e4c120bc 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -153,21 +153,20 @@ export namespace GooglePhotos {
             }
             const tagMapping = new Map<string, string>();
             const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto);
-            images && images.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
-            const values = Object.values(ContentCategories);
+            images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
+            const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE);
             for (const value of values) {
-                if (value === ContentCategories.NONE) {
-                    continue;
-                }
-                for (const id of (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id)) {
+                const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id);
+                console.log("Searching " + value);
+                console.log(searched);
+                searched?.forEach(async id => {
                     const image = await Cast(idMapping[id], Doc);
-                    if (!image) {
-                        continue;
+                    if (image) {
+                        const key = image[Id];
+                        const tags = tagMapping.get(key);
+                        !tags?.includes(value) && tagMapping.set(key, tags + delimiter + value);
                     }
-                    const key = image[Id];
-                    const tags = tagMapping.get(key);
-                    !tags?.includes(value) && tagMapping.set(key, tags + delimiter + value);
-                }
+                });
             }
             images?.forEach(image => {
                 const concatenated = tagMapping.get(image[Id])!;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8c78e568c..250c9b3b0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,7 +6,7 @@ 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 { Cast } from "../../../new_fields/Types";
+import { Cast, ScriptCast } from "../../../new_fields/Types";
 import { GestureUtils } from "../../../pen-gestures/GestureUtils";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { Upload } from "../../../server/SharedMediaTypes";
@@ -208,19 +208,18 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
-            (this.props.Document.dropConverter instanceof ScriptField) &&
-                this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
+            ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
             if (docDragData) {
                 let added = false;
                 if (docDragData.dropAction || docDragData.userDropAction) {
-                    added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+                    added = this.props.addDocument(docDragData.droppedDocuments as any as Doc);
                 } else if (docDragData.moveDocument) {
-                    const movedDocs = docDragData.draggedDocuments;
-                    added = movedDocs.reduce((added: boolean, d, i) =>
-                        docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
-                            docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
+                    const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
+                    const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
+                    const res = addedDocs.length ? this.props.addDocument(addedDocs as any as Doc) : true;
+                    added = movedDocs.length ? docDragData.moveDocument(movedDocs as any as Doc, this.props.Document, this.props.addDocument) : res;
                 } else {
-                    added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+                    added = this.props.addDocument(docDragData.droppedDocuments as any as Doc);
                 }
                 e.stopPropagation();
                 return added;
@@ -389,8 +388,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                         console.log(result);
                         const json = JSON.parse(result as string) as any;
                         addDocument(Docs.Create.TreeDocument(
-                            json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) =>
-                                Docs.Create.LabelDocument({ title: c["#text"], _width: 120, _height: 20 })
+                            json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => {
+                                const label = Docs.Create.LabelDocument({ title: c["#text"], _width: 120, _height: 20 });
+                                const proto = Doc.GetProto(label)
+                                proto._width = 120;
+                                proto._height = 20;
+                                return proto;
+                            }
                             ), { _width: 150, _height: 600, title: "across", backgroundColor: "white", _singleLine: true }));
                     });
                 }
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index d43dd387a..7877fe155 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -70,7 +70,7 @@
     height: 30px;
     position: absolute;
     bottom: 15px;
-    left: 15px;
+    right: 15px;
     border: 2px solid black;
     border-radius: 50%;
     padding: 3px;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 43da1c44f..ccfc660cc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -15,9 +15,7 @@ import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
 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';
-import { SelectionManager } from '../../util/SelectionManager';
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { ScriptBox } from '../ScriptBox';
@@ -45,7 +43,6 @@ 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';
 import { CollectionPileView } from './CollectionPileView';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -119,35 +116,32 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
     @action.bound
-    addDocument(doc: Doc): boolean {
-        if (this.props.filterAddDocument?.(doc) === false) {
-            return false;
+    addDocument = (doc: Doc): boolean => {
+        if (doc instanceof Doc) {
+            if (this.props.filterAddDocument?.(doc) === false) {
+                return false;
+            }
         }
-
+        const docs = doc instanceof Doc ? [doc] : doc as any as Doc[];
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
-        !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc]));  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
-        // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
-        targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-        doc.context = this.props.Document;
-        Doc.GetProto(doc).lastOpened = new DateField;
+        const added = docs.filter(d => !docList.includes(d));
+        if (added.length) {
+            added.map(doc => doc.context = this.props.Document);
+            targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
+            targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+        }
         return true;
     }
 
     @action.bound
-    removeDocument(doc: Doc): boolean {
+    removeDocument = (doc: any): boolean => {
+        const docs = doc instanceof Doc ? [doc] : doc as Doc[];
         const targetDataDoc = this.props.Document[DataSym];
-        const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
-        docView && SelectionManager.DeselectDoc(docView);
         const value = DocListCast(targetDataDoc[this.props.fieldKey]);
-        let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
-        index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
-
-        doc.context = undefined;
-        ContextMenu.Instance?.clearItems();
-        if (index !== -1) {
-            value.splice(index, 1);
-            targetDataDoc[this.props.fieldKey] = new List<Doc>(value);
+        const result = value.filter(v => !docs.includes(v));
+        if (result.length !== value.length) {
+            targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
             return true;
         }
         return false;
@@ -158,7 +152,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     // otherwise, the document being moved must be able to be removed from its container before
     // moving it into the target.  
     @action.bound
-    moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
+    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
         if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
@@ -166,10 +160,14 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     }
 
     showIsTagged = () => {
-        const children = DocListCast(this.props.Document[this.props.fieldKey]);
-        const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
-        const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
-        return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
+        return (null);
+        // this section would display an icon in the bototm right of a collection to indicate that all 
+        // photos had been processed through Google's content analysis API and Google's tags had been
+        // assigned to the documents googlePhotosTags field.
+        // const children = DocListCast(this.props.Document[this.props.fieldKey]);
+        // const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
+        // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
+        // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
     }
 
     private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 37957b0f6..6011d5f9c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -124,10 +124,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.addDocument(newBox);
     }
     private addDocument = (newBox: Doc) => {
-        const added = this.props.addDocument(newBox);
-        added && this.bringToFront(newBox);
-        added && this.updateCluster(newBox);
-        return added;
+        if (newBox instanceof Doc) {
+            const added = this.props.addDocument(newBox);
+            added && this.bringToFront(newBox);
+            added && this.updateCluster(newBox);
+            return added;
+        } else {
+            return this.props.addDocument(newBox);
+            // bcz: deal with clusters
+        }
     }
     private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
@@ -153,6 +158,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const xfo = this.getTransformOverlay();
         const [xp, yp] = xf.transformPoint(de.x, de.y);
         const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
+        const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
         if (super.onInternalDrop(e, de)) {
             if (de.complete.docDragData) {
                 if (de.complete.docDragData.droppedDocuments.length) {
@@ -162,20 +168,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
                     const dropX = NumCast(firstDoc.x);
                     const dropY = NumCast(firstDoc.y);
-                    de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => {
-                        const layoutDoc = Doc.Layout(d);
-                        d.x = x + NumCast(d.x) - dropX;
-                        d.y = y + NumCast(d.y) - dropY;
-                        if (!NumCast(layoutDoc._width)) {
-                            layoutDoc._width = 300;
-                        }
-                        if (!NumCast(layoutDoc._height)) {
-                            const nw = NumCast(layoutDoc._nativeWidth);
-                            const nh = NumCast(layoutDoc._nativeHeight);
-                            layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
+                    const droppedDocs = de.complete.docDragData.droppedDocuments;
+                    runInAction(() => {
+                        zsorted.forEach((doc, index) => doc.zIndex = index + 1);
+                        for (let i = 0; i < droppedDocs.length; i++) {
+                            const d = droppedDocs[i];
+                            const layoutDoc = Doc.Layout(d);
+                            d.x = x + NumCast(d.x) - dropX;
+                            d.y = y + NumCast(d.y) - dropY;
+                            if (!NumCast(layoutDoc._width)) {
+                                layoutDoc._width = 300;
+                            }
+                            if (!NumCast(layoutDoc._height)) {
+                                const nw = NumCast(layoutDoc._nativeWidth);
+                                const nh = NumCast(layoutDoc._nativeHeight);
+                                layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
+                            }
+                            d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i);
                         }
-                        d.isBackground === undefined && this.bringToFront(d);
-                    }));
+                    });
 
                     (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
                 }
@@ -760,20 +771,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    bringToFront = (doc: Doc, sendToBack?: boolean) => {
+    bringToFront = action((doc: Doc, sendToBack?: boolean) => {
         if (sendToBack || doc.isBackground) {
             doc.zIndex = 0;
         }
         else {
             const docs = this.childLayoutPairs.map(pair => pair.layout);
-            docs.slice().sort((doc1, doc2) => {
-                if (doc1 === doc) return 1;
-                if (doc2 === doc) return -1;
-                return NumCast(doc1.zIndex) - NumCast(doc2.zIndex);
-            }).forEach((doc, index) => doc.zIndex = index + 1);
-            doc.zIndex = docs.length + 1;
+            docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+            let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1;
+            if (zlast - docs.length > 100) {
+                for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+                zlast = docs.length + 1;
+            }
+            doc.zIndex = zlast + 1;
         }
-    }
+    })
 
     scaleAtPt(docpt: number[], scale: number) {
         const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -895,14 +907,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     addDocTab = action((doc: Doc, where: string) => {
         if (where === "inParent") {
-            const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
-            doc.x = pt[0];
-            doc.y = pt[1];
-            this.props.addDocument(doc);
-            return true;
+            if (doc instanceof Doc) {
+                const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+                doc.x = pt[0];
+                doc.y = pt[1];
+                return this.props.addDocument(doc);
+            } else {
+                (doc as any as Doc[]).forEach(doc => {
+                    const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+                    doc.x = pt[0];
+                    doc.y = pt[1];
+                });
+                return this.props.addDocument(doc);
+            }
         }
         if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
-            this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+            this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
             return true;
         }
         return this.props.addDocTab(doc, where);
@@ -1002,8 +1022,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
-        runInAction(() =>
-            Array.from(newPool.entries()).map(entry => {
+        const array = Array.from(newPool.entries());
+        runInAction(() => {
+            for (let i = 0; i < array.length; i++) {
+                const entry = array[i];
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
                 if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
@@ -1012,7 +1034,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
                     this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
                 }
-            }));
+            }
+        });
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements: ViewDefResult[] = computedElementData.slice();
@@ -1058,12 +1081,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     promoteCollection = undoBatch(action(() => {
-        this.childDocs.forEach(doc => {
+        const childDocs = this.childDocs.slice();
+        childDocs.forEach(doc => {
             const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
             doc.x = scr?.[0];
             doc.y = scr?.[1];
-            this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
         });
+        this.props.addDocTab(childDocs as any as Doc, "inParent");
         this.props.ContainingCollectionView?.removeDocument(this.props.Document);
     }));
     layoutDocsInGrid = () => {
@@ -1148,6 +1172,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @action
     setupDragLines = () => {
+        const activeDocs = this.getActiveDocuments();
+        if (activeDocs.length > 50) {
+            DragManager.SetSnapLines([], []);
+            return;
+        }
         const size = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
         const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5bac075b4..43d4203ad 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -198,7 +198,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        if (!this.props.active(true)) this.props.selectDocuments([this.props.Document], []);
         if (this._visible) {
             const mselect = this.marqueeSelect();
             if (!e.shiftKey) {
@@ -300,10 +299,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     delete = () => {
-        this.marqueeSelect(false).map(d => this.props.removeDocument(d));
-        if (this.ink) {
-            // this.marqueeInkDelete(this.ink.inkData);
-        }
+        this.props.removeDocument(this.marqueeSelect(false) as any as Doc);
         SelectionManager.DeselectAll();
         this.cleanupInteractions(false);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -349,13 +345,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const bounds = this.Bounds;
         const selected = this.marqueeSelect(false);
         if (e instanceof KeyboardEvent ? e.key === "c" : true) {
-            selected.map(d => {
-                this.props.removeDocument(d);
+            selected.map(action(d => {
+                //this.props.removeDocument(d);
                 d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                 d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
                 d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
                 return d;
-            });
+            }));
+            this.props.removeDocument(selected as any as Doc);
         }
         const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
         this.props.addDocument(newCollection);
-- 
cgit v1.2.3-70-g09d2


From fcd0895e5933270718b9c1a262e7e377eaabb024 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 7 May 2020 19:18:52 -0400
Subject: fixed most add/remove/move documents to support doc[]'s

---
 src/client/util/DragManager.ts                     |  4 +-
 src/client/views/DocComponent.tsx                  | 37 +++++++++----
 src/client/views/MainView.tsx                      |  6 +-
 src/client/views/TemplateMenu.tsx                  |  6 +-
 src/client/views/collections/CollectionSubView.tsx | 14 ++---
 .../views/collections/CollectionTreeView.tsx       | 64 +++++++++++++---------
 src/client/views/collections/CollectionView.tsx    |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 12 ++--
 src/client/views/nodes/FieldView.tsx               |  6 +-
 src/client/views/nodes/PresBox.tsx                 |  9 ++-
 src/client/views/nodes/VideoBox.tsx                |  9 ++-
 13 files changed, 107 insertions(+), 72 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0d208cf1b..cc8cc38dd 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -77,8 +77,8 @@ export namespace DragManager {
         return root;
     }
     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 type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+    export type RemoveFunction = (document: Doc | Doc[]) => boolean;
 
     export interface DragDropDisposer { (): void; }
     export interface DragOptions {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 11866aebe..f602cbb15 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym } from '../../new_fields/Doc';
+import { Doc, Opt, DataSym, DocListCast } from '../../new_fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
 import { Cast, BoolCast, ScriptCast } from '../../new_fields/Types';
@@ -6,6 +6,8 @@ import { listSpec } from '../../new_fields/Schema';
 import { InkingControl } from './InkingControl';
 import { InkTool } from '../../new_fields/InkField';
 import { InteractionUtils } from '../util/InteractionUtils';
+import { List } from '../../new_fields/List';
+import { DateField } from '../../new_fields/DateField';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -99,22 +101,37 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         public get annotationKey() { return this._annotationKey; }
 
         @action.bound
-        removeDocument(doc: Doc): boolean {
-            Doc.GetProto(doc).annotationOn = undefined;
-            const value = Cast(this.dataDoc[this.props.fieldKey + "-" + this._annotationKey], listSpec(Doc), []);
-            const index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
-            return index !== -1 && value && value.splice(index, 1) ? true : false;
+        removeDocument(doc: Doc | Doc[]): boolean {
+            const docs = doc instanceof Doc ? [doc] : doc;
+            docs.map(doc => doc.annotationOn = undefined);
+            const targetDataDoc = this.dataDoc;
+            const value = DocListCast(targetDataDoc[this.props.fieldKey + "-" + this._annotationKey]);
+            const result = value.filter(v => !docs.includes(v));
+            if (result.length !== value.length) {
+                targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+                return true;
+            }
+            return false;
         }
         // if the moved document is already in this overlay collection nothing needs to be done.
         // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. 
         @action.bound
-        moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
+        moveDocument(doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean {
             return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false;
         }
         @action.bound
-        addDocument(doc: Doc): boolean {
-            doc.context = Doc.GetProto(doc).annotationOn = this.props.Document;
-            return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false;
+        addDocument(doc: Doc | Doc[]): boolean {
+            const docs = doc instanceof Doc ? [doc] : doc;
+            docs.map(doc => doc.context = Doc.GetProto(doc).annotationOn = this.props.Document);
+            const targetDataDoc = this.props.Document[DataSym];
+            const docList = DocListCast(targetDataDoc[this.props.fieldKey + "-" + this._annotationKey]);
+            const added = docs.filter(d => !docList.includes(d));
+            if (added.length) {
+                added.map(doc => doc.context = this.props.Document);
+                targetDataDoc[this.props.fieldKey + "-" + this._annotationKey] = new List<Doc>([...docList, ...added]);
+                targetDataDoc[this.props.fieldKey + "-" + this._annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+            }
+            return true;
         }
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e67f2f3a2..5b838446d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -520,9 +520,9 @@ export class MainView extends React.Component {
         return !this._flyoutTranslate ? (<div className="mainView-expandFlyoutButton" title="Re-attach sidebar" onPointerDown={MainView.expandFlyout}><FontAwesomeIcon icon="chevron-right" color="grey" size="lg" /></div>) : (null);
     }
 
-    addButtonDoc = (doc: Doc) => Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc);
-    remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc);
-    moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
+    addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+    remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+    moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
 
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 665ab4e41..5f300372f 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -158,9 +158,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 annotationsKey={""}
                 dontRegisterView={true}
                 fieldKey={"data"}
-                moveDocument={(doc: Doc) => false}
-                removeDocument={(doc: Doc) => false}
-                addDocument={(doc: Doc) => false} />
+                moveDocument={returnFalse}
+                removeDocument={returnFalse}
+                addDocument={returnFalse} />
         </ul>;
     }
 }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 250c9b3b0..ae84b1923 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -27,9 +27,9 @@ import { CollectionView } from "./CollectionView";
 import React = require("react");
 
 export interface CollectionViewProps extends FieldViewProps {
-    addDocument: (document: Doc) => boolean;
-    removeDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    addDocument: (document: Doc | Doc[]) => boolean;
+    removeDocument: (document: Doc | Doc[]) => boolean;
+    moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
     VisibleHeight?: () => number;
@@ -212,14 +212,14 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             if (docDragData) {
                 let added = false;
                 if (docDragData.dropAction || docDragData.userDropAction) {
-                    added = this.props.addDocument(docDragData.droppedDocuments as any as Doc);
+                    added = this.props.addDocument(docDragData.droppedDocuments);
                 } else if (docDragData.moveDocument) {
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
-                    const res = addedDocs.length ? this.props.addDocument(addedDocs as any as Doc) : true;
-                    added = movedDocs.length ? docDragData.moveDocument(movedDocs as any as Doc, this.props.Document, this.props.addDocument) : res;
+                    const res = addedDocs.length ? this.props.addDocument(addedDocs) : true;
+                    added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, this.props.addDocument) : res;
                 } else {
-                    added = this.props.addDocument(docDragData.droppedDocuments as any as Doc);
+                    added = this.props.addDocument(docDragData.droppedDocuments);
                 }
                 e.stopPropagation();
                 return added;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4fb54cc07..815202cfe 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -44,7 +44,7 @@ export interface TreeViewProps {
     containingCollection: Doc;
     prevSibling?: Doc;
     renderDepth: number;
-    deleteDoc: (doc: Doc) => boolean;
+    deleteDoc: (doc: Doc | Doc[]) => boolean;
     moveDocument: DragManager.MoveFunction;
     dropAction: dropActionType;
     addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
@@ -52,7 +52,7 @@ export interface TreeViewProps {
     panelWidth: () => number;
     panelHeight: () => number;
     ChromeHeight: undefined | (() => number);
-    addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
+    addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
     indentDocument?: () => void;
     outdentDocument?: () => void;
     ScreenToLocalTransform: () => Transform;
@@ -133,14 +133,16 @@ class TreeView extends React.Component<TreeViewProps> {
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
     @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath);
     @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
-    @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => {
+    @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
     }
-    @undoBatch @action remove = (document: Document, key: string) => {
-        return Doc.RemoveDocFromList(this.dataDoc, key, document);
+    @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
+        return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
+            flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
     }
-    @undoBatch @action removeDoc = (document: Document) => {
-        return Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), document);
+    @undoBatch @action removeDoc = (doc: Doc | Doc[]) => {
+        return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
+            flg && Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), doc), true);
     }
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
@@ -224,9 +226,10 @@ class TreeView extends React.Component<TreeViewProps> {
         if (de.complete.docDragData) {
             e.stopPropagation();
             if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
-            let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before);
+            let addDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
             if (inside) {
-                addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
+                addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce(
+                    ((flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc)), true) || addDoc(doc);
             }
             const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
             const move = de.complete.docDragData.dropAction === "move" || de.complete.docDragData.dropAction;
@@ -286,8 +289,9 @@ class TreeView extends React.Component<TreeViewProps> {
             let contentElement: (JSX.Element | null)[] | JSX.Element = [];
 
             if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) {
-                const remDoc = (doc: Doc) => this.remove(doc, key);
-                const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+                const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
+                const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce(
+                    (flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true);
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
@@ -330,8 +334,9 @@ class TreeView extends React.Component<TreeViewProps> {
         TraceMobx();
         const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
         if (expandKey !== undefined) {
-            const remDoc = (doc: Doc) => this.remove(doc, expandKey);
-            const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true);
+            const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey);
+            const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) =>
+                (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true);
             const docs = expandKey === "links" ? this.childLinks : this.childDocs;
             const sortKey = `${this.fieldKey}-sortAscending`;
             return <ul key={expandKey + "more"} onClick={(e) => {
@@ -464,6 +469,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         ref={this._docRef}
                         Document={this.props.document}
                         DataDoc={undefined}
+                        treeViewId={this.props.treeViewId[Id]}
                         LibraryPath={this.props.libraryPath || emptyPath}
                         addDocument={undefined}
                         addDocTab={this.props.addDocTab}
@@ -529,8 +535,8 @@ class TreeView extends React.Component<TreeViewProps> {
         key: string,
         parentCollectionDoc: Doc | undefined,
         parentPrevSibling: Doc | undefined,
-        add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
-        remove: ((doc: Doc) => boolean),
+        add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean,
+        remove: ((doc: Doc | Doc[]) => boolean),
         move: DragManager.MoveFunction,
         dropAction: dropActionType,
         addDocTab: (doc: Doc, where: string) => boolean,
@@ -619,7 +625,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     remove(child);
                 }
             };
-            const addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
+            const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => {
                 return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false);
             };
             const childLayout = Doc.Layout(pair.layout);
@@ -689,22 +695,26 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     }
 
     @action
-    remove = (document: Document): boolean => {
-        const children = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
-        if (children.indexOf(document) !== -1) {
-            children.splice(children.indexOf(document), 1);
+    remove = (doc: Doc | Doc[]): boolean => {
+        const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+        const targetDataDoc = this.props.Document[DataSym];
+        const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+        const result = value.filter(v => !docs.includes(v));
+        if (result.length !== value.length) {
+            targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
             return true;
         }
         return false;
     }
     @action
-    addDoc = (doc: Document, relativeTo: Opt<Doc>, before?: boolean): boolean => {
-        const doAddDoc = () =>
-            Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false);
+    addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
+        const doAddDoc = (doc: Doc | Doc[]) =>
+            (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) =>
+                flg && Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true);
         if (this.props.Document.resolvedDataDoc instanceof Promise) {
-            this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc());
+            this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc(doc));
         } else {
-            doAddDoc();
+            doAddDoc(doc);
         }
         return true;
     }
@@ -788,8 +798,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
         const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType;
-        const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
-        const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
+        const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
+        const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(d, target, addDoc);
         const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
         return !childDocs ? (null) : (
             <div className="collectionTreeView-dropTarget" id="body"
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ccfc660cc..b96f8150d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -116,13 +116,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
     @action.bound
-    addDocument = (doc: Doc): boolean => {
+    addDocument = (doc: Doc | Doc[]): boolean => {
         if (doc instanceof Doc) {
             if (this.props.filterAddDocument?.(doc) === false) {
                 return false;
             }
         }
-        const docs = doc instanceof Doc ? [doc] : doc as any as Doc[];
+        const docs = doc instanceof Doc ? [doc] : doc;
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6011d5f9c..b32318d66 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -123,7 +123,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox);
     }
-    private addDocument = (newBox: Doc) => {
+    private addDocument = (newBox: Doc | Doc[]) => {
         if (newBox instanceof Doc) {
             const added = this.props.addDocument(newBox);
             added && this.bringToFront(newBox);
@@ -184,7 +184,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                                 const nh = NumCast(layoutDoc._nativeHeight);
                                 layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
                             }
-                            d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i);
+                            d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
                         }
                     });
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 43d4203ad..2230c391c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -299,7 +299,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     delete = () => {
-        this.props.removeDocument(this.marqueeSelect(false) as any as Doc);
+        this.props.removeDocument(this.marqueeSelect(false));
         SelectionManager.DeselectAll();
         this.cleanupInteractions(false);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -352,7 +352,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
                 return d;
             }));
-            this.props.removeDocument(selected as any as Doc);
+            this.props.removeDocument(selected);
         }
         const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
         this.props.addDocument(newCollection);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1724d39cc..042869004 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -70,12 +70,13 @@ export interface DocumentViewProps {
     onDoubleClick?: ScriptField;
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
+    treeViewId?: string;
     dropAction?: dropActionType;
     dragDivName?: string;
     nudge?: (x: number, y: number) => void;
-    addDocument?: (doc: Doc) => boolean;
-    removeDocument?: (doc: Doc) => boolean;
-    moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    addDocument?: (doc: Doc | Doc[]) => boolean;
+    removeDocument?: (doc: Doc | Doc[]) => boolean;
+    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     setupDragLines?: () => void;
     renderDepth: number;
@@ -243,6 +244,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;//  this.Document.onDragStart ? undefined : this.props.moveDocument;
             dragData.dragDivName = this.props.dragDivName;
+            dragData.treeViewId = this.props.treeViewId;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
         }
     }
@@ -1043,7 +1045,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor.  This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
 
     @undoBatch
-    hideLinkAnchor = (doc: Doc) => doc.hidden = true
+    hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true)
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     @computed get anchors() {
@@ -1143,7 +1145,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
         let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
-        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} 
+        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
             id={this.props.Document[Id]}
             ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 7db634e74..2ab0a416d 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -31,11 +31,11 @@ export interface FieldViewProps {
     select: (isCtrlPressed: boolean) => void;
     rootSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
-    addDocument?: (document: Doc) => boolean;
+    addDocument?: (document: Doc | Doc[]) => boolean;
     addDocTab: (document: Doc, where: string) => boolean;
     pinToPres: (document: Doc) => void;
-    removeDocument?: (document: Doc) => boolean;
-    moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    removeDocument?: (document: Doc | Doc[]) => boolean;
+    moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     backgroundColor?: (document: Doc) => string | undefined;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 343e74c87..49862d39a 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -271,9 +271,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     });
 
     whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
-    addDocumentFilter = (doc: Doc) => {
-        doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
-        !this.childDocs.includes(doc) && (doc.presZoomButton = true);
+    addDocumentFilter = (doc: Doc|Doc[]) => {
+        const docs = doc instanceof Doc ? [doc]: doc;
+        docs.forEach(doc => {
+            doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
+            !this.childDocs.includes(doc) && (doc.presZoomButton = true);
+        });
         return true;
     }
     childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 266b7f43f..208b93ba6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -337,9 +337,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     }
 
     @action.bound
-    addDocumentWithTimestamp(doc: Doc): boolean {
-        const curTime = (this.layoutDoc.currentTimecode || -1);
-        curTime !== -1 && (doc.displayTimecode = curTime);
+    addDocumentWithTimestamp(doc: Doc|Doc[]): boolean {
+        const docs = doc instanceof Doc ? [doc] : doc;
+        docs.forEach(doc => {
+            const curTime = (this.layoutDoc.currentTimecode || -1);
+            curTime !== -1 && (doc.displayTimecode = curTime);
+        })
         return this.addDocument(doc);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 4914965affb984e04e847460ce1b5750807b316f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 9 May 2020 15:56:11 -0400
Subject: fixed missing gear in golden layout.  fixed double dragging events. 
 changed dragging to give prefernce to modifier keys over dropactions. 
 changed names of Documents to Catalog.   added document copy past with ctrl
 c/ctrl v

---
 src/client/util/DragManager.ts                         |  7 ++-----
 src/client/views/GlobalKeyHandler.ts                   | 10 ++++++++++
 src/client/views/MainView.tsx                          |  3 +--
 src/client/views/PreviewCursor.tsx                     | 13 +++++++++++++
 src/client/views/collections/CollectionDockingView.tsx |  2 +-
 src/client/views/collections/CollectionSubView.tsx     | 10 ++++++----
 src/client/views/collections/CollectionTreeView.tsx    |  3 +--
 src/client/views/collections/CollectionView.tsx        | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx                | 15 ++++++++++-----
 src/server/authentication/models/current_user_utils.ts | 14 +++++++-------
 10 files changed, 56 insertions(+), 31 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cc8cc38dd..4f547e2f7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -176,7 +176,7 @@ export namespace DragManager {
         element: HTMLElement,
         dropFunc: (e: Event, de: DropEvent) => void,
         doc?: Doc,
-        preDropFunc?: (e: Event, de: DropEvent) => void,
+        preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void,
     ): DragDropDisposer {
         if ("canDrop" in element.dataset) {
             throw new Error(
@@ -187,10 +187,7 @@ export namespace DragManager {
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
         const preDropHandler = (e: Event) => {
             const de = (e as CustomEvent<DropEvent>).detail;
-            if (de.complete.docDragData && doc?.targetDropAction) {
-                de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
-            }
-            preDropFunc?.(e, de);
+            preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType);
         };
         element.addEventListener("dashOnDrop", handler);
         doc && element.addEventListener("dashPreDrop", preDropHandler);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c73e1a66a..d9281ac24 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -15,6 +15,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
 import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
 import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
+import { Id } from "../../new_fields/FieldSymbols";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -240,8 +241,17 @@ export default class KeyManager {
                 break;
             case "a":
             case "v":
+                stopPropagation = false;
+                preventDefault = false;
+                break;
             case "x":
             case "c":
+                SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText("__DashDocId:" + SelectionManager.SelectedDocuments()[0].Document[Id]);
+                // window.getSelection()?.removeAllRanges();
+                // let range = document.createRange();
+                // range.selectNode(SelectionManager.SelectedDocuments()[0].ContentDiv!);
+                // window.getSelection()?.addRange(range);
+                // document.execCommand('copy');
                 stopPropagation = false;
                 preventDefault = false;
                 break;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fad6bf295..b6dfe60e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -249,8 +249,7 @@ export class MainView extends React.Component {
             title: "Collection " + workspaceCount,
         };
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
-        Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myDocuments as Doc), "data", freeformDoc);
-        const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myDocuments as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+        const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
 
         const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
         mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!]);
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 80c7f3d25..b9036bf1e 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -6,6 +6,7 @@ import "./PreviewCursor.scss";
 import { Docs } from '../documents/Documents';
 import { Doc } from '../../new_fields/Doc';
 import { Transform } from "../util/Transform";
+import { DocServer } from '../DocServer';
 
 @observer
 export class PreviewCursor extends React.Component<{}> {
@@ -49,6 +50,18 @@ export class PreviewCursor extends React.Component<{}> {
                     }));
                 }
 
+                if (e.clipboardData.getData("text/plain").includes("__DashDocId:")) {
+                    const docid = e.clipboardData.getData("text/plain").split("__DashDocId:")[1];
+                    return DocServer.GetRefField(docid).then(doc => {
+                        if (doc instanceof Doc) {
+                            const alias = Doc.MakeAlias(doc);
+                            alias.x = newPoint[0];
+                            alias.y = newPoint[1];
+                            PreviewCursor._addDocument(alias);
+                        }
+                    });
+                }
+
                 // creates text document
                 return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", {
                     _width: 500,
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c1edc0fe7..6fdb96f0d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -527,7 +527,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                                 gearSpan);
                             tab.buttonDisposer?.();
                         }
-                    });
+                    }, { fireImmediately: true });
 
                 tab.reactComponents = [gearSpan];
                 tab.element.append(gearSpan);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1c2d9a76..4fef16315 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -16,7 +16,7 @@ import { DocServer } from "../../DocServer";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Networking } from "../../Network";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { InteractionUtils } from "../../util/InteractionUtils";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -195,10 +195,12 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
         }
 
-        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) {
+        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
             if (de.complete.docDragData) {
-                if (de.complete.docDragData.draggedDocuments.some(d => this.childDocs.includes(d))) {
-                    de.complete.docDragData.dropAction = "move";
+                // if targetDropAction is, say 'alias', but we're just dragging within a collection, we want to ignore the targetAction.
+                // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
+                if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+                    de.complete.docDragData.dropAction = targetAction;
                 }
                 e.stopPropagation();
             }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 58d021b5c..7086ba380 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -410,7 +410,6 @@ class TreeView extends React.Component<TreeViewProps> {
     @computed
     get renderTitle() {
         TraceMobx();
-        const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true);
         (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(self, 'editTitle', true)"));
         const headerElements = (
             <>
@@ -432,7 +431,7 @@ class TreeView extends React.Component<TreeViewProps> {
             <FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" />
         </div>);
         return <>
-            <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} onPointerDown={onItemDown}
+            <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`}
                 style={{
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined,
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b96f8150d..6c96baede 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -69,15 +69,15 @@ export enum CollectionViewType {
     Pile = "pileup"
 }
 export interface CollectionViewCustomProps {
-    filterAddDocument: (doc: Doc) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+    filterAddDocument: (doc: Doc | Doc[]) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     childLayoutTemplate?: () => Opt<Doc>;  // specify a layout Doc template to use for children of the collection
     childLayoutString?: string;  // specify a layout string to use for children of the collection
 }
 
 export interface CollectionRenderProps {
-    addDocument: (document: Doc) => boolean;
-    removeDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    addDocument: (document: Doc | Doc[]) => boolean;
+    removeDocument: (document: Doc | Doc[]) => boolean;
+    moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     PanelWidth: () => number;
@@ -152,7 +152,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     // otherwise, the document being moved must be able to be removed from its container before
     // moving it into the target.  
     @action.bound
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
         if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e3b69b8f3..ebfc38a54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../new_fields/Doc";
 import { Document } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -15,7 +15,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { ClientRecommender } from '../../ClientRecommender';
 import { DocServer } from "../../DocServer";
@@ -549,7 +549,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, this.props.dropAction ? this.props.dropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+                    this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -721,6 +721,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         let open = cm.findByDescription("Add a Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+        openItems.push({ description: "New Echo        ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "layer-group" });
         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" });
         if (!open) {
@@ -864,8 +865,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
         cm.addItem({
             description: `path: ${path}`, event: () => {
-                this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
-                Doc.linkFollowHighlight(this.props.Document);
+                if (this.props.LibraryPath !== emptyPath) {
+                    this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
+                    Doc.linkFollowHighlight(this.props.Document);
+                } else {
+                    Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
+                }
             }, icon: "check"
         });
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a776b6f10..bebf77a8c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -507,13 +507,13 @@ export class CurrentUserUtils {
 
         return doc.myWorkspaces as Doc;
     }
-    static setupDocumentCollection(doc: Doc) {
-        if (doc.myDocuments === undefined) {
-            doc.myDocuments = new PrefetchProxy(Docs.Create.TreeDocument([], {
-                title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
+    static setupCatalog(doc: Doc) {
+        if (doc.myCatalog === undefined) {
+            doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], {
+                title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
             }));
         }
-        return doc.myDocuments as Doc;
+        return doc.myCatalog as Doc;
     }
     static setupRecentlyClosed(doc: Doc) {
         // setup Recently Closed library item
@@ -533,7 +533,7 @@ export class CurrentUserUtils {
     // setup the Library button which will display the library panel.  This panel includes a collection of workspaces, documents, and recently closed views
     static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) {
         const workspaces = CurrentUserUtils.setupWorkspaces(doc);
-        const documents = CurrentUserUtils.setupDocumentCollection(doc);
+        const documents = CurrentUserUtils.setupCatalog(doc);
         const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc);
 
         if (doc["tabs-button-library"] === undefined) {
@@ -541,7 +541,7 @@ export class CurrentUserUtils {
                 _width: 50, _height: 25, title: "Library", _fontSize: 10,
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
-                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
+                    title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
                 })) as any as Doc,
                 targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
                 onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
-- 
cgit v1.2.3-70-g09d2


From 5c876f2f456f75e9886946f738f07a730688f38a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 9 May 2020 17:44:14 -0400
Subject: lint fixes

---
 src/Utils.ts                                                 |  2 +-
 src/client/DocServer.ts                                      |  2 +-
 src/client/views/DocComponent.tsx                            |  2 +-
 src/client/views/collections/CollectionSchemaCells.tsx       |  6 +++---
 .../views/collections/CollectionSchemaMovableTableHOC.tsx    |  8 ++++----
 src/client/views/collections/CollectionSchemaView.tsx        |  6 +++---
 src/client/views/collections/CollectionSubView.tsx           |  6 +++---
 src/client/views/collections/CollectionTreeView.tsx          |  2 +-
 src/client/views/collections/CollectionView.tsx              |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  2 +-
 src/client/views/linking/LinkMenuItem.tsx                    |  2 +-
 src/client/views/nodes/VideoBox.tsx                          |  2 +-
 src/client/views/nodes/formattedText/DashFieldView.tsx       |  5 +++--
 src/new_fields/Doc.ts                                        | 12 ++++++------
 14 files changed, 30 insertions(+), 29 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 732f74bfc..bcb215804 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -323,7 +323,7 @@ export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?
     pattern && Array.from(Object.keys(omit)).filter(key => key.match(pattern)).forEach(key => {
         extract[key] = omit[key];
         delete omit[key];
-    })
+    });
     addKeyFunc?.(omit);
     return { omit, extract };
 }
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index bf9fd232c..34ef502ad 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -250,7 +250,7 @@ export namespace DocServer {
         if (cached !== undefined && !(cached instanceof Promise)) {
             return cached;
         }
-    }
+    };
 
     let _GetRefField: (id: string) => Promise<Opt<RefField>> = errorFunc;
     let _GetCachedRefField: (id: string) => Opt<RefField> = errorFunc;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 4e7d4c5ed..881e352a6 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -103,7 +103,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         removeDocument(doc: Doc | Doc[]): boolean {
             const docs = doc instanceof Doc ? [doc] : doc;
             docs.map(doc => doc.annotationOn = undefined);
-            const targetDataDoc = this.dataDoc; this
+            const targetDataDoc = this.dataDoc;
             const value = DocListCast(targetDataDoc[this.annotationKey]);
             const result = value.filter(v => !docs.includes(v));
             if (result.length !== value.length) {
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 5253ee0b9..8a5450b0c 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -37,8 +37,8 @@ export interface CellProps {
     renderDepth: number;
     addDocTab: (document: Doc, where: string) => boolean;
     pinToPres: (document: Doc) => void;
-    moveDocument: (document: Doc, targetCollection: Doc | undefined,
-        addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined,
+        addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     isFocused: boolean;
     changeFocusedCellByIndex: (row: number, col: number) => void;
     setIsEditing: (isEditing: boolean) => void;
@@ -185,7 +185,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         const onItemDown = (e: React.PointerEvent) => {
             fieldIsDoc && SetupDrag(this._focusRef,
                 () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
-                this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc | undefined, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
+                this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument,
                 this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
         };
         const onPointerEnter = (e: React.PointerEvent): void => {
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 8636e3eb5..5aec46a83 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -130,8 +130,8 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
 export interface MovableRowProps {
     rowInfo: RowInfo;
     ScreenToLocalTransform: () => Transform;
-    addDoc: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
-    removeDoc: (doc: Doc) => boolean;
+    addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
+    removeDoc: (doc: Doc | Doc[]) => boolean;
     rowFocused: boolean;
     textWrapRow: (doc: Doc) => void;
     rowWrapped: boolean;
@@ -183,7 +183,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
         if (docDragData) {
             e.stopPropagation();
             if (docDragData.draggedDocuments[0] === rowDoc) return true;
-            const addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before);
+            const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
             const movedDocs = docDragData.draggedDocuments;
             return (docDragData.dropAction || docDragData.userDropAction) ?
                 docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
@@ -201,7 +201,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
 
     @undoBatch
     @action
-    move: DragManager.MoveFunction = (doc: Doc, targetCollection: Doc | undefined, addDoc) => {
+    move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
         const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
         if (targetView && targetView.props.ContainingCollectionDoc) {
             return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index c0024293f..51ad6c81b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -207,9 +207,9 @@ export interface SchemaTableProps {
     ContainingCollectionDoc: Opt<Doc>;
     fieldKey: string;
     renderDepth: number;
-    deleteDocument: (document: Doc) => boolean;
-    addDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    deleteDocument: (document: Doc | Doc[]) => boolean;
+    addDocument: (document: Doc | Doc[]) => boolean;
+    moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     active: (outsideReaction: boolean) => boolean;
     onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4fef16315..8b50bd8fc 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -246,7 +246,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 };
                 temporaryFileReader.readAsText(inputFile);
             });
-        };
+        }
         @undoBatch
         @action
         protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
@@ -388,11 +388,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
                     file?.type === "application/json" && this.readUploadedFileAsText(file).then(result => {
                         console.log(result);
-                        const json = JSON.parse(result as string) as any;
+                        const json = JSON.parse(result as string);
                         addDocument(Docs.Create.TreeDocument(
                             json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => {
                                 const label = Docs.Create.LabelDocument({ title: c["#text"], _width: 120, _height: 20 });
-                                const proto = Doc.GetProto(label)
+                                const proto = Doc.GetProto(label);
                                 proto._width = 120;
                                 proto._height = 20;
                                 return proto;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7086ba380..f6fcc1ac4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -673,7 +673,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
 
     @action
     remove = (doc: Doc | Doc[]): boolean => {
-        const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+        const docs = doc instanceof Doc ? [doc] : doc;
         const targetDataDoc = this.props.Document[DataSym];
         const value = DocListCast(targetDataDoc[this.props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 6c96baede..0841d9680 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -152,7 +152,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     // otherwise, the document being moved must be able to be removed from its container before
     // moving it into the target.  
     @action.bound
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
         if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dfcb30588..6cc0cfcd2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -786,7 +786,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
             doc.zIndex = zlast + 1;
         }
-    })
+    });
 
     scaleAtPt(docpt: number[], scale: number) {
         const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d4e58fa8e..b0c61cb04 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -38,7 +38,7 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
         }
 
         const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
-        dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
+        dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
             docView.props.removeDocument?.(doc);
             addDocument(doc);
             return true;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 208b93ba6..9602eac52 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -342,7 +342,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         docs.forEach(doc => {
             const curTime = (this.layoutDoc.currentTimecode || -1);
             curTime !== -1 && (doc.displayTimecode = curTime);
-        })
+        });
         return this.addDocument(doc);
     }
 
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f28057fd7..d5a28fd14 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -82,7 +82,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     // set the display of the field's value (checkbox for booleans, span of text for strings)
     @computed get fieldValueContent() {
         if (this._dashDoc) {
-            const dashVal = this._dashDoc![this._fieldKey] || (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
+            const dashVal = this._dashDoc[this._fieldKey] || (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
             const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
             const boolVal = Cast(fval, "boolean", null);
             const strVal = Field.toString(fval as Field) || "";
@@ -153,8 +153,9 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 } else if (nodeText.startsWith("=:=")) {
                     Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
                 } else {
-                    if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS)
+                    if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
                         this._dashDoc![this._fieldKey] = newText;
+                    }
                 }
             });
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 2d4a4539e..e0627fb37 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -592,8 +592,8 @@ export namespace Doc {
     }
 
     export function MakeClone(doc: Doc): Doc {
-        let cloneMap = new Map<string, Doc>();
-        let rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
+        const cloneMap = new Map<string, Doc>();
+        const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
         const copy = Doc.makeClone(doc, cloneMap, rtfMap);
         rtfMap.map(({ copy, key, field }) => {
             const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
@@ -605,7 +605,7 @@ export namespace Doc {
                 return href + (mapped ? mapped[Id] : id);
             };
             const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
-            var re = new RegExp(regex, "g");
+            const re = new RegExp(regex, "g");
             copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
         });
         return copy;
@@ -626,9 +626,9 @@ export namespace Doc {
                 const list = Cast(doc[key], listSpec(Doc));
                 if (list !== undefined && !(list instanceof Promise)) {
                     copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs)));
-                } else if (doc[key] instanceof Doc)
+                } else if (doc[key] instanceof Doc) {
                     copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields 
-                else {
+                } else {
                     copy[key] = ObjectField.MakeCopy(field);
                     if (field instanceof RichTextField) {
                         if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
@@ -636,7 +636,7 @@ export namespace Doc {
                         }
                     }
                 }
-            }
+            };
             if (key === "proto") {
                 if (doc[key] instanceof Doc) {
                     copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs);
-- 
cgit v1.2.3-70-g09d2


From 5726d19a615385b0bcdf1e3a944056d31074edbb Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 10 May 2020 17:02:04 -0400
Subject: made opening docs on right not create aliases.  made clicking on an
 original document trigger replacing it with an alias.

---
 .../views/collections/CollectionDockingView.tsx    | 38 +++++++++++++++++-----
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 ++++
 src/client/views/nodes/DocumentView.tsx            | 13 +++++---
 5 files changed, 47 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6fdb96f0d..6e9b2386d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -71,8 +71,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         (window as any).ReactDOM = ReactDOM;
         DragManager.StartWindowDrag = this.StartOtherDrag;
     }
-    hack: boolean = false;
-    undohack: any = null;
     public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
         let config: any;
         if (dragDocs.length === 1) {
@@ -192,6 +190,29 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return retVal;
     }
 
+    @undoBatch
+    @action
+    public static ReplaceTab(document: Doc, stack: any): Opt<Doc> {
+        if (!CollectionDockingView.Instance) return undefined;
+        const instance = CollectionDockingView.Instance;
+        const replaceTab = (doc: Doc, child: any): Opt<Doc> => {
+            for (let i = 0; i < child.contentItems.length; i++) {
+                if (child.contentItems[i].isRow || child.contentItems[i].isColumn || child.contentItems[i].isStack) {
+                    const val = replaceTab(doc, child.contentItems[i]);
+                    if (val) return val;
+                } else if (child.contentItems[i].config.component === "DocumentFrameRenderer" &&
+                    child.contentItems[i].config.props.documentId === doc[Id]) {
+                    const alias = Doc.MakeAlias(doc);
+                    child.contentItems[i].config.props.documentId = alias[Id];
+                    child.contentItems[i].config.title = alias.title;
+                    instance.stateChanged();
+                    return alias;
+                }
+            }
+            return undefined;
+        }
+        return replaceTab(document, instance._goldenLayout.root);
+    }
 
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split
@@ -455,12 +476,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         const json = JSON.stringify(this._goldenLayout.toConfig());
         this.props.Document.dockingConfig = json;
         this.updateDataField(json);
-
-        if (this.undohack && !this.hack) {
-            this.undohack.end();
-            this.undohack = undefined;
-        }
-        this.hack = false;
     }
 
     itemDropped = () => {
@@ -789,6 +804,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return CollectionDockingView.AddRightSplit(doc, libraryPath);
         } else if (location === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
+        } else if (location === "replace") {
+            const alias = CollectionDockingView.ReplaceTab(doc, this._stack);
+            if (alias) {
+                runInAction(() => this._document = alias);
+                return true;
+            }
+            return false;
         } else {// if (location === "inPlace") {
             return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
         }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8cd34e7ed..2f332e77d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -108,7 +108,7 @@ class TreeView extends React.Component<TreeViewProps> {
             Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey]));
     }
 
-    @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath);
+    @undoBatch openRight = () => this.props.addDocTab(this.props.document, "onRight", this.props.libraryPath);
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0841d9680..ac9f64d94 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -128,6 +128,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const added = docs.filter(d => !docList.includes(d));
         if (added.length) {
             added.map(doc => doc.context = this.props.Document);
+            added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
             targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
             targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ed3e50f45..61081618a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -339,6 +339,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+
+            if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) {
+                this.props.addDocTab(this.props.Document, "replace");
+                e.stopPropagation();
+                e.preventDefault();
+                return;
+            }
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ebfc38a54..d2dabe7ec 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -305,11 +305,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         }, console.log);
                         func();
                     } else {
-                        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");
+                        UndoManager.RunInBatch(() => {
+                            if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
+                                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+                                fullScreenAlias.layoutKey = "layout_fullScreen";
+                                this.props.addDocTab(fullScreenAlias, "inTab");
+                                this.props.addDocTab(this.props.Document, "inTab");
+                            }
+                        }, "double tap");
                         SelectionManager.DeselectAll();
                         Doc.UnBrushDoc(this.props.Document);
                     }
-- 
cgit v1.2.3-70-g09d2


From f1473fc5bf7f2b3109be358ae14d28725240284c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 11 May 2020 22:30:33 -0400
Subject: cleaned up menu items.  improved snapping with fixed aspect items,
 but not perfect.

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/DragManager.ts                     | 14 ++--
 src/client/views/DocumentDecorations.tsx           | 41 ++++++++++--
 src/client/views/MainView.tsx                      |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionView.tsx    |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++---
 src/client/views/nodes/DocumentView.tsx            | 75 ++++++++--------------
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 37 ++++-------
 10 files changed, 101 insertions(+), 99 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a87a77e1d..95b8daafc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -150,6 +150,7 @@ export interface DocumentOptions {
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     clipboard?: Doc;
+    UseCors?: boolean;
     icon?: string;
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4f547e2f7..1ee4c57a2 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -256,7 +256,8 @@ export namespace DragManager {
         SnappingManager.setSnapLines(horizLines, vertLines);
     }
 
-    export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
+    // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line 
+    export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, oneAxis: boolean = false) {
         const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
         const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
             if (snapLines.length) {
@@ -265,14 +266,15 @@ export namespace DragManager {
                 const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest));
                 const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i]));
                 const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2;
-                return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag;
+                return closestDists[minIndex] < snapThreshold ? [closestDists[minIndex], closestPts[minIndex] + offs[minIndex]] : [Number.MAX_VALUE, drag];
             }
-            return drag;
+            return [Number.MAX_VALUE, drag];
         };
-
+        const xsnap = snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines());
+        const ysnap = snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines());
         return {
-            thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
-            thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines())
+            thisX: !oneAxis || xsnap[0] < ysnap[0] ? xsnap[1] : e.pageX,
+            thisY: !oneAxis || xsnap[0] > ysnap[0] ? ysnap[1] : e.pageY
         };
     }
     export let docsBeingDragged: Doc[] = [];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a3c476125..b939d96b6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -20,7 +20,6 @@ import React = require("react");
 import { Id } from '../../new_fields/FieldSymbols';
 import e = require('express');
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { MainView } from './MainView';
 import { SnappingManager } from '../util/SnappingManager';
 
 library.add(faCaretUp);
@@ -238,6 +237,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     _initialAutoHeight = false;
+    _dragHeights = new Map<Doc, number>();
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
@@ -253,17 +253,38 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         this._snapX = e.pageX;
         this._snapY = e.pageY;
         this._initialAutoHeight = true;
+        DragManager.docsBeingDragged = SelectionManager.SelectedDocuments().map(dv => dv.rootDoc);
+        SelectionManager.SelectedDocuments().map(dv => {
+            this._dragHeights.set(dv.layoutDoc, NumCast(dv.layoutDoc._height));
+            dv.layoutDoc._delayAutoHeight = dv.layoutDoc._height;
+        });
     }
 
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
-        const { thisX, thisY } = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
+        const first = SelectionManager.SelectedDocuments()[0];
+        const fixedAspect = NumCast(first.layoutDoc._nativeWidth) !== 0;
+        let { thisX, thisY } = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY, fixedAspect);
+        if (fixedAspect) {  // if aspect is set, then any snapped movement must be coerced to match the aspect ratio
+            const aspect = NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight);
+            const deltaX = thisX - this._snapX;
+            const deltaY = thisY - this._snapY;
+            if (thisX !== e.pageX) {
+                const snapY = deltaX / aspect + this._snapY;
+                thisY = Math.abs(deltaX / aspect) < 10 ? snapY : thisY;
+            } else {
+                const snapX = deltaY * aspect + this._snapX;
+                thisX = Math.abs(deltaY * aspect) < 10 ? snapX : thisX;
+            }
+        }
         move[0] = thisX - this._snapX;
         move[1] = thisY - this._snapY;
         this._snapX = thisX;
         this._snapY = thisY;
 
         let dX = 0, dY = 0, dW = 0, dH = 0;
-
+        const unfreeze = () =>
+            SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
+                (element.rootDoc.type === DocumentType.RTF && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
         switch (this._resizeHdlId) {
             case "": break;
             case "documentDecorations-topLeftResizer":
@@ -278,6 +299,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 dH = -move[1];
                 break;
             case "documentDecorations-topResizer":
+                unfreeze();
                 dY = -1;
                 dH = -move[1];
                 break;
@@ -291,13 +313,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 dH = move[1];
                 break;
             case "documentDecorations-bottomResizer":
+                unfreeze();
                 dH = move[1];
                 break;
             case "documentDecorations-leftResizer":
+                unfreeze();
                 dX = -1;
                 dW = -move[0];
                 break;
             case "documentDecorations-rightResizer":
+                unfreeze();
                 dW = move[0];
                 break;
         }
@@ -309,9 +334,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let nwidth = doc._nativeWidth || 0;
                 let nheight = doc._nativeHeight || 0;
                 const width = (doc._width || 0);
-                const height = (doc._height || (nheight / nwidth * width));
+                let height = (doc._height || (nheight / nwidth * width));
                 const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
                 if (nwidth && nheight) {
+                    if (nwidth / nheight !== width / height) {
+                        height = nheight / nwidth * width;
+                    }
                     if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
                     else dW = dH * nwidth / nheight;
                 }
@@ -365,7 +393,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action
     onPointerUp = (e: PointerEvent): void => {
         SelectionManager.SelectedDocuments().map(dv => {
-            dv.layoutDoc._delayAutoHeight && (dv.layoutDoc._autoHeight = true);
+            if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) {
+                dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.props.ContentScaling(), dv.panelWidth(), dv.panelHeight());
+                dv.layoutDoc._autoHeight = true;
+            }
             dv.layoutDoc._delayAutoHeight = undefined;
         });
         this._resizeHdlId = "";
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 62496b01f..9bfef06b4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -83,14 +83,14 @@ export class MainView extends React.Component {
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.addEventListener("keydown", KeyManager.Instance.handle);
-        window.addEventListener("paste", KeyManager.Instance.paste);
+        window.addEventListener("paste", KeyManager.Instance.paste as any);
     }
 
     componentWillUnMount() {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.removeEventListener("pointerdown", this.globalPointerDown);
         window.removeEventListener("pointerup", this.globalPointerUp);
-        window.removeEventListener("paste", KeyManager.Instance.paste);
+        window.removeEventListener("paste", KeyManager.Instance.paste as any);
     }
 
     constructor(props: Readonly<{}>) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 50e3b73eb..2e24cbb12 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -757,7 +757,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     get layoutDoc() { return this._document && Doc.Layout(this._document); }
     nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0;
     panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) :
-        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth);
+        (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth)
     panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight;
 
     nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ac9f64d94..0f239d385 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -234,7 +234,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     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("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
             this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 129b245b8..6caee960d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1133,19 +1133,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
-        if (this.props.children && this.props.annotationsKey) return;
+        if (this.props.annotationsKey) return;
+
+        ContextMenu.Instance.addItem({
+            description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
+                this._timelineVisible = !this._timelineVisible;
+            }), icon: this._timelineVisible ? faEyeSlash : faEye
+        });
+
         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.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
-        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         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" });
         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: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
@@ -1173,14 +1179,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 input.click();
             }
         });
+        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
         ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
-
-        ContextMenu.Instance.addItem({
-            description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
-                this._timelineVisible = !this._timelineVisible;
-            }), icon: this._timelineVisible ? faEyeSlash : faEye
-        });
     }
     @observable _timelineVisible = false;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5cccec776..1bf297796 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -704,7 +704,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const cm = ContextMenu.Instance;
-        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
@@ -713,25 +712,16 @@ 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" }));
 
 
-        let open = cm.findByDescription("Add a Perspective...");
-        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-        openItems.push({ description: "New Echo        ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "layer-group" });
-        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" });
-        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 : [];
+        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+        optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+        templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         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 : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
@@ -764,9 +754,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
             // cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
         }
-        if (Cast(this.props.Document.data, ImageField)) {
-            moreItems.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
-        }
         if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
             moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
             moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
@@ -820,47 +807,38 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
         runInAction(() => {
-            if (!ClientUtils.RELEASE) {
-                const setWriteMode = (mode: DocServer.WriteMode) => {
-                    DocServer.AclsMode = mode;
-                    const mode1 = mode;
-                    const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
-                    DocServer.setFieldWriteMode("x", mode1);
-                    DocServer.setFieldWriteMode("y", mode1);
-                    DocServer.setFieldWriteMode("_width", mode1);
-                    DocServer.setFieldWriteMode("_height", mode1);
-
-                    DocServer.setFieldWriteMode("_panX", mode2);
-                    DocServer.setFieldWriteMode("_panY", mode2);
-                    DocServer.setFieldWriteMode("scale", mode2);
-                    DocServer.setFieldWriteMode("_viewType", mode2);
-                };
-                const aclsMenu: ContextMenuProps[] = [];
-                aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
-                aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
-                aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
-                aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
-                cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
-            }
+            const setWriteMode = (mode: DocServer.WriteMode) => {
+                DocServer.AclsMode = mode;
+                const mode1 = mode;
+                const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+                DocServer.setFieldWriteMode("x", mode1);
+                DocServer.setFieldWriteMode("y", mode1);
+                DocServer.setFieldWriteMode("_width", mode1);
+                DocServer.setFieldWriteMode("_height", mode1);
+
+                DocServer.setFieldWriteMode("_panX", mode2);
+                DocServer.setFieldWriteMode("_panY", mode2);
+                DocServer.setFieldWriteMode("scale", mode2);
+                DocServer.setFieldWriteMode("_viewType", mode2);
+            };
+            const aclsMenu: ContextMenuProps[] = [];
+            aclsMenu.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
+            aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+            aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+            aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+            aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+            cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
         });
         runInAction(() => {
-            cm.addItem({
-                description: "Share",
-                event: () => SharingManager.Instance.open(this),
-                icon: "external-link-alt"
-            });
-
             if (!this.topMost && !(e instanceof Touch)) {
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-            if (!SelectionManager.IsSelected(this, true)) {
-                SelectionManager.SelectDoc(this, false);
-            }
+            !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
-        cm.addItem({
+        const item = ({
             description: `path: ${path}`, event: () => {
                 if (this.props.LibraryPath !== emptyPath) {
                     this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
@@ -870,6 +848,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 }
             }, icon: "check"
         });
+        //cm.addItem(item);
     }
 
     recommender = async () => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d85373e98..aaebceaa2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -29,6 +29,7 @@ import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 const requestImageSize = require('../../util/request-image-size');
 const path = require('path');
 const { Howl } = require('howler');
@@ -158,6 +159,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         if (field) {
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
+            funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+            funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             // funcs.push({
             //     description: "Reset Native Dimensions", event: action(async () => {
             //         const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
@@ -184,7 +187,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
 
             const existingMore = ContextMenu.Instance.findByDescription("More...");
             const mores: ContextMenuProps[] = existingMore && "subitems" in existingMore ? existingMore.subitems : [];
-            mores.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
             !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" });
         }
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3a586ff66..23bf86a32 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -419,9 +419,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         const funcs: ContextMenuProps[] = [];
         this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         !this.layoutDoc.isTemplateDoc && funcs.push({
-            description: "Make Template", event: () => {
+            description: "Convert to use as a style", event: () => {
                 this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc);
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
@@ -444,11 +443,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }, icon: "eye"
         });
         //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        funcs.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
+        funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const uicontrols: ContextMenuProps[] = [];
+        uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
+        uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
+        uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
 
         const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
@@ -481,19 +484,6 @@ 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("Add a Perspective...");
-        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-
-        openItems.push({
-            description: "FreeForm", event: undoBatch(() => {
-                const alias = Doc.MakeAlias(this.rootDoc);
-                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
-                this.props.addDocTab(alias, "onRight");
-            }), icon: "eye"
-        });
-        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
-
     }
 
     recordDictation = () => {
@@ -663,11 +653,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._disposers.height = reaction(
             () => this.layoutDoc[HeightSym](),
             action(height => {
-                if (height <= 20) {
-                    if (this.layoutDoc._nativeWidth || this.layoutDoc._nativeHeight) {
-                        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight());
-                    }
-                    this.layoutDoc._delayAutoHeight = true;
+                if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
+                    this.layoutDoc._delayAutoHeight = height;
                 }
             })
         );
@@ -1202,7 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const dh = NumCast(this.rootDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
-                if (this.rootDoc !== this.layoutDoc && !this.layoutDoc.resolvedDataDoc) {
+                if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
                     // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
                     console.log("Delayed height adjustment...");
                     setTimeout(() => {
-- 
cgit v1.2.3-70-g09d2

