From 6df3143ead642356b7823946e9710f840f3faa5a Mon Sep 17 00:00:00 2001
From: _stanleyyip <33562077+yipstanley@users.noreply.github.com>
Date: Tue, 14 May 2019 16:02:37 -0400
Subject: argh

---
 src/client/views/nodes/DocumentContentsView.tsx | 1 +
 1 file changed, 1 insertion(+)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f404b7bc6..11df9162a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,6 +23,7 @@ import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
+import { PDFBox2 } from "../pdf/PDFBox2";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
-- 
cgit v1.2.3-70-g09d2


From 464fa03d6ebb2a7aaef1d7622afa3e1e7ee816a3 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 18 Jun 2019 20:11:31 -0400
Subject: Context menu improvements and error fixes

---
 src/client/views/ContextMenu.scss                  | 22 +++++-----
 src/client/views/ContextMenuItem.tsx               | 47 +++++++++++++++++++---
 .../views/collections/CollectionSchemaView.tsx     |  1 +
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentView.tsx            |  5 ++-
 5 files changed, 57 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 7e066d53a..e363c5158 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -8,19 +8,19 @@
     flex-direction: column;
 }
 
-.contextMenu-item:first-child {
-    background: $intermediate-color;
-    color: $light-color;
-}
+// .contextMenu-item:first-child {
+//     background: $intermediate-color;
+//     color: $light-color;
+// }
 
-.contextMenu-item:first-child::placeholder {
-    color: $light-color;
-}
+// .contextMenu-item:first-child::placeholder {
+//     color: $light-color;
+// }
 
-.contextMenu-item:first-child:hover {
-    background: $intermediate-color;
-    color: $light-color;
-}
+// .contextMenu-item:first-child:hover {
+//     background: $intermediate-color;
+//     color: $light-color;
+// }
 
 .contextMenu-subMenu-cont {
     position: absolute;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index fcda0db89..dc0751049 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,9 +1,12 @@
 import React = require("react");
 import { observable, action } from "mobx";
 import { observer } from "mobx-react";
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 
+library.add(faAngleRight);
+
 export interface OriginalMenuProps {
     description: string;
     event: (e: React.MouseEvent<HTMLDivElement>) => void;
@@ -14,6 +17,7 @@ export interface OriginalMenuProps {
 export interface SubmenuProps {
     description: string;
     subitems: ContextMenuProps[];
+    icon?: IconProp; //maybe should be optional (icon?)
     closeMenu?: () => void;
 }
 
@@ -41,13 +45,40 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
         }
     }
 
+    currentTimeout?: any;
+    static readonly timeout = 300;
+    onPointerEnter = () => {
+        if (this.currentTimeout) {
+            clearTimeout(this.currentTimeout);
+            this.currentTimeout = undefined;
+        }
+        if (this.overItem) {
+            return;
+        }
+        this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
+    }
+
+    onPointerLeave = () => {
+        if (this.currentTimeout) {
+            clearTimeout(this.currentTimeout);
+            this.currentTimeout = undefined;
+        }
+        if (!this.overItem) {
+            return;
+        }
+        this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
+
+    }
+
     render() {
         if ("event" in this.props) {
             return (
                 <div className="contextMenu-item" onClick={this.handleEvent}>
-                    <span className="icon-background">
-                        {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : <FontAwesomeIcon icon="circle" size="sm" />}
-                    </span>
+                    {this.props.icon ? (
+                        <span className="icon-background">
+                            <FontAwesomeIcon icon={this.props.icon} size="sm" />
+                        </span>
+                    ) : null}
                     <div className="contextMenu-description">
                         {this.props.description}
                     </div>
@@ -60,9 +91,15 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
             return (
-                <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })} onMouseLeave={action(() => this.overItem = false)}>
+                <div className="contextMenu-item" onMouseEnter={this.onPointerEnter} onMouseLeave={this.onPointerLeave}>
+                    {this.props.icon ? (
+                        <span className="icon-background">
+                            <FontAwesomeIcon icon={this.props.icon} size="sm" />
+                        </span>
+                    ) : null}
                     <div className="contextMenu-description">
                         {this.props.description}
+                        <FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "5px" }} />
                     </div>
                     {submenu}
                 </div>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4b46c73c1..14a7d19d0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -352,6 +352,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             height={this.previewHeight}
             getTransform={this.getPreviewTransform}
             CollectionView={this.props.CollectionView}
+            moveDocument={this.props.moveDocument}
             addDocument={this.props.addDocument}
             removeDocument={this.props.removeDocument}
             active={this.props.active}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c2caabb92..02396c3af 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,7 +23,6 @@ import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
-import { PDFBox2 } from "../pdf/PDFBox2";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index de6e9f5fa..7c058d91c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faUnlock, faLock, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
+import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faUnlock, faLock, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faShare, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
@@ -35,6 +35,7 @@ import { RouteStore } from '../../../server/RouteStore';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(faTrash);
+library.add(faShare);
 library.add(faExpandArrowsAlt);
 library.add(faCompressArrowsAlt);
 library.add(faLayerGroup);
@@ -510,7 +511,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
         }));
         runInAction(() => {
-            cm.addItem({ description: "Share...", subitems: usersMenu });
+            cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
             if (!this.topMost) {
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From e495d4e32402384dbf7bb72945e83c912fce892b Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 20 Jun 2019 17:46:56 -0400
Subject: first shot at templates...

---
 src/client/views/MainOverlayTextBox.tsx            |  6 ++--
 src/client/views/MainView.tsx                      |  1 +
 .../views/collections/CollectionDockingView.tsx    |  4 ++-
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 .../views/collections/CollectionStackingView.tsx   |  4 ++-
 src/client/views/collections/CollectionView.tsx    | 37 ++++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/DocumentContentsView.tsx    | 12 ++++---
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        | 32 ++++++++++---------
 11 files changed, 77 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b4ad5f4d7..64271650b 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -37,7 +37,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             (box?: FormattedTextBox) => {
                 this._textBox = box;
                 if (box) {
-                    this.TextDoc = box.props.Document;
+                    this.TextDoc = box.props.DataDoc;
                     let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
                     let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); };
                     this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
@@ -118,7 +118,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: "0px" }}>
                     <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
-                        <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document}
+                        <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true}
+                            Document={FormattedTextBox.InputBoxOverlay.props.Document}
+                            DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                             isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true}
                             ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                             ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 51630c29b..8198b88d2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -203,6 +203,7 @@ export class MainView extends React.Component {
         let mainCont = this.mainContainer;
         let content = !mainCont ? (null) :
             <DocumentView Document={mainCont}
+                DataDoc={mainCont}
                 addDocument={undefined}
                 addDocTab={emptyFunction}
                 removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 69b9e77eb..9a25e090c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -492,7 +492,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
-                <DocumentView key={this._document[Id]} Document={this._document}
+                <DocumentView key={this._document[Id]}
+                    Document={this._document}
+                    DataDoc={this._document}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9cc8961e3..c5b0527cc 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -98,6 +98,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     renderCell = (rowProps: CellInfo) => {
         let props: FieldViewProps = {
             Document: rowProps.original,
+            DataDoc: rowProps.original,
             fieldKey: rowProps.column.id as string,
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
@@ -423,7 +424,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
         return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width(), height: "100%" }}>
             {!this.props.Document || !this.props.width ? (null) : (
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}>
-                    <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
+                    <DocumentView DataDoc={this.props.Document} Document={this.props.Document} isTopMost={false} selectOnLoad={false}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument}
                         ScreenToLocalTransform={this.getTransform}
                         ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 368e94a8c..8e61181f4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -119,7 +119,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     gridColumnEnd: `span 1`,
                     transform: `scale(${renderScale})`
                 }} >
-                <DocumentView key={d[Id]} Document={d}
+                <DocumentView key={d[Id]}
+                    Document={d}
+                    DataDoc={d}
                     addDocument={this.props.addDocument}
                     removeDocument={this.props.removeDocument}
                     moveDocument={this.moveDocument}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 68eefab4c..6d3301ce4 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -14,6 +14,10 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
+import { Doc } from '../../../new_fields/Doc';
+import { FormattedTextBox } from '../nodes/FormattedTextBox';
+import { Docs } from '../../documents/Documents';
+import { List } from '../../../new_fields/List';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -54,6 +58,39 @@ export class CollectionView extends React.Component<FieldViewProps> {
             subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
             subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
             ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
+            ContextMenu.Instance.addItem({
+                description: "Add Description Template", event: undoBatch(() => {
+                    Doc.GetProto(this.props.Document).description = "my first templated box";
+                    let template = Doc.MakeAlias(this.props.Document);
+                    template.layout = FormattedTextBox.LayoutString("description");
+                    template.x = 0;
+                    template.y = 0;
+                    template.width = 100;
+                    template.height = 25;
+                    Doc.AddDocToList(this.props.Document, "data", template);
+                }), icon: "project-diagram"
+            });
+            ContextMenu.Instance.addItem({
+                description: "Add Summary Template", event: undoBatch(() => {
+                    Doc.GetProto(this.props.Document).summary = "my first templated box";
+                    let template = Doc.MakeAlias(this.props.Document);
+                    template.layout = FormattedTextBox.LayoutString("summary");
+                    template.x = 0;
+                    template.y = 0;
+                    template.width = 100;
+                    template.height = 25;
+                    Doc.AddDocToList(this.props.Document, "data", template);
+                }), icon: "project-diagram"
+            });
+            ContextMenu.Instance.addItem({
+                description: "Apply Template", event: undoBatch(() => {
+                    let otherdoc = Docs.TextDocument({ width: 100, height: 50, title: "applied template" });
+                    Doc.GetProto(otherdoc).description = "THIS DESCRIPTION IS REALLY IMPORTANT!";
+                    Doc.GetProto(otherdoc).summary = "THIS SUMMARY IS MEANINGFUL!";
+                    Doc.GetProto(otherdoc).layout = this.props.Document;
+                    this.props.addDocTab && this.props.addDocTab(otherdoc, "onRight");
+                }), icon: "project-diagram"
+            });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 84841e469..013c11837 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -296,6 +296,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getDocumentViewProps(document: Doc): DocumentViewProps {
         return {
             Document: document,
+            DataDoc: this.props.DataDoc,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 02396c3af..a4316c143 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,6 +23,7 @@ import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
+import { Doc } from "../../../new_fields/Doc";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -47,11 +48,12 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     hideOnLeave?: boolean
 }> {
     @computed get layout(): string {
-        const layout = Cast(this.props.Document[this.props.layoutKey], "string");
+        let layoutDoc = this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+        const layout = Cast(layoutDoc[this.props.layoutKey], "string");
         if (layout === undefined) {
             return this.props.Document.data ?
                 "<FieldView {...props} fieldKey='data' />" :
-                KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : "");
+                KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : "");
         } else if (typeof layout === "string") {
             return layout;
         } else {
@@ -59,8 +61,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         }
     }
 
-    CreateBindings(): JsxBindings {
-        return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
+    CreateBindings(layoutDoc?: Doc): JsxBindings {
+        return { props: { ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit, Document: layoutDoc } };
     }
 
     @computed get templates(): List<string> {
@@ -104,7 +106,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
-            bindings={this.CreateBindings()}
+            bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
             jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 280804630..93b883533 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -67,6 +67,7 @@ const LinkDoc = makeInterface(linkSchema);
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
+    DataDoc: Doc;
     addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
     removeDocument?: (doc: Doc) => boolean;
     moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1f1582f22..35608af86 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,6 +30,7 @@ export interface FieldViewProps {
     fieldKey: string;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
     Document: Doc;
+    DataDoc: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 376b5a574..8231cc089 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -104,19 +104,21 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    @computed get dataDoc() { return this.props.DataDoc; }
+
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
             this._applyingChange = true;
-            Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
-            Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+            Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
+            Doc.SetOnPrototype(this.dataDoc, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
             this._applyingChange = false;
-            let title = StrCast(this.props.Document.title);
+            let title = StrCast(this.dataDoc.title);
             if (title && title.startsWith("-") && this._editorView) {
                 let str = this._editorView.state.doc.textContent;
                 let titlestr = str.substr(0, Math.min(40, str.length));
-                let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+                let target = this.dataDoc.proto ? this.dataDoc.proto : this.dataDoc;
                 target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
             }
         }
@@ -144,14 +146,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else {
             if (de.data instanceof DragManager.DocumentDragData) {
-                let ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc));
+                let ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
                 if (!ldocs) {
-                    this.props.Document.subBulletDocs = new List<Doc>([]);
+                    this.dataDoc.subBulletDocs = new List<Doc>([]);
                 }
-                ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc));
+                ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
                 if (!ldocs) return;
                 if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) {
-                    ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.props.Document.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
+                    ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.dataDoc.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
                     this.props.addDocument && this.props.addDocument(ldocs[0] as Doc);
                     this.props.Document.templates = new List<string>([Templates.Bullet.Layout]);
                     this.props.Document.isBullet = true;
@@ -201,13 +203,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
         this._reactionDisposer = reaction(
             () => {
-                const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
+                const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
                 return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
             },
             field => this._editorView && !this._applyingChange &&
                 this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
         );
-        this.setupEditor(config, this.props.Document, this.props.fieldKey);
+        this.setupEditor(config, this.dataDoc, this.props.fieldKey);
     }
 
     private setupEditor(config: any, doc: Doc, fieldKey: string) {
@@ -360,10 +362,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         // stop propagation doesn't seem to stop propagation of native keyboard events.
         // so we set a flag on the native event that marks that the event's been handled.
         (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
-        if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+        if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView) {
             let str = this._editorView.state.doc.textContent;
             let titlestr = str.substr(0, Math.min(40, str.length));
-            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+            let target = this.dataDoc.proto ? this.dataDoc.proto : this.dataDoc;
             target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
         }
         if (!this._undoTyping) {
@@ -372,11 +374,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this.props.isOverlay && this.props.Document.autoHeight) {
             let xf = this._ref.current!.getBoundingClientRect();
             let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
-            let nh = NumCast(this.props.Document.nativeHeight, 0);
+            let nh = NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
             let sh = scrBounds.height;
             this.props.Document.height = nh ? dh / nh * sh : sh;
-            this.props.Document.proto!.nativeHeight = nh ? sh : undefined;
+            this.dataDoc.proto!.nativeHeight = nh ? sh : undefined;
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 45ded4da2592bc2a8201e1260dfb6a80485684c7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 25 Jun 2019 09:35:09 -0400
Subject: converted isTopMost into renderDepth and capped renderDepth at 7. 
 removed previous cycle detection.

---
 src/client/documents/Documents.ts                  |  6 +--
 src/client/util/DragManager.ts                     |  2 -
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionBaseView.tsx       | 48 ++++------------------
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionSchemaView.tsx     |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 ++--
 src/client/views/nodes/DocumentContentsView.tsx    |  1 +
 src/client/views/nodes/DocumentView.tsx            | 11 ++---
 src/client/views/nodes/FieldView.tsx               |  4 +-
 src/client/views/nodes/KeyValuePair.tsx            |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 14 files changed, 31 insertions(+), 65 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b04fc401a..ac8a892b8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -344,7 +344,7 @@ export namespace Docs {
         {layout}
     </div>
     <div style="height:(100% + 25px); width:100%; position:absolute">
-        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
     </div>
 </div>       
         `);
@@ -356,7 +356,7 @@ export namespace Docs {
             {layout}
         </div>
         <div style="height:25px; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
         </div>
     </div>       
             `);
@@ -379,7 +379,7 @@ export namespace Docs {
             {layout}
         </div>
         <div style="height:15%; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
         </div>
     </div>       
             `);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 3143924fc..442187912 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -28,7 +28,6 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
         document.removeEventListener('pointerup', onRowUp);
     };
     let onItemDown = async (e: React.PointerEvent) => {
-        // if (this.props.isSelected() || this.props.isTopMost) {
         if (e.button === 0) {
             e.stopPropagation();
             if (e.shiftKey && CollectionDockingView.Instance) {
@@ -38,7 +37,6 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
                 document.addEventListener("pointerup", onRowUp);
             }
         }
-        //}
     };
     return onItemDown;
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9d5844426..042306997 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -153,7 +153,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
-            if (documentView.props.isTopMost) {
+            if (documentView.props.renderDepth === 0) {
                 return bounds;
             }
             let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b2cfc82c4..7363b08ef 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -123,7 +123,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                         <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true}
                             Document={FormattedTextBox.InputBoxOverlay.props.Document}
                             DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
-                            isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true}
+                            isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
                             ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                             ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(dominus: HTMLElement) => this._dominus = dominus} />
                     </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a72f25b99..d26e24748 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -211,7 +211,7 @@ export class MainView extends React.Component {
                 ContentScaling={returnOne}
                 PanelWidth={this.getPWidth}
                 PanelHeight={this.getPHeight}
-                isTopMost={true}
+                renderDepth={0}
                 selectOnLoad={false}
                 focus={emptyFunction}
                 parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 79a9f3be0..50d1a5071 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -64,8 +64,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     active = (): boolean => {
         var isSelected = this.props.isSelected();
-        var topMost = this.props.isTopMost;
-        return isSelected || this._isChildActive || topMost;
+        return isSelected || this._isChildActive || this.props.renderDepth === 0;
     }
 
     //TODO should this be observable?
@@ -75,32 +74,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         this.props.whenActiveChanged(isActive);
     }
 
-    createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean {
-        if (!(documentToAdd instanceof Doc)) {
-            return false;
-        }
-        if (StrCast(documentToAdd.layout).indexOf("CollectionView") !== -1) {
-            let data = DocListCast(documentToAdd.data);
-            for (const doc of data) {
-                if (this.createsCycle(doc, containerDocument)) {
-                    return true;
-                }
-            }
-        }
-        let annots = DocListCast(documentToAdd.annotations);
-        for (const annot of annots) {
-            if (this.createsCycle(annot, containerDocument)) {
-                return true;
-            }
-        }
-        for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) {
-            if (containerProto[Id] === documentToAdd[Id]) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
         var curPage = NumCast(this.props.Document.curPage, -1);
@@ -109,19 +82,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
         allowDuplicates = true;
-        if (!this.createsCycle(doc, this.dataDoc)) {
-            //TODO This won't create the field if it doesn't already exist
-            const value = Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc));
-            if (value !== undefined) {
-                if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
-                    value.push(doc);
-                }
-            } else {
-                Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new List([doc]));
+        //TODO This won't create the field if it doesn't already exist
+        const value = Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc));
+        if (value !== undefined) {
+            if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+                value.push(doc);
             }
-            return true;
+        } else {
+            Doc.SetOnPrototype(this.dataDoc, this.props.fieldKey, new List([doc]));
         }
-        return false;
+        return true;
     }
 
     @action.bound
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 82cb5dbbb..6d2f61173 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -535,7 +535,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     PanelWidth={this.nativeWidth}
                     PanelHeight={this.nativeHeight}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    isTopMost={true}
+                    renderDepth={0}
                     selectOnLoad={false}
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b4158a5b1..f03afe7e0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -104,7 +104,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
             select: emptyFunction,
-            isTopMost: false,
+            renderDepth: this.props.renderDepth + 1,
             selectOnLoad: false,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
@@ -454,7 +454,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}>
                     <DocumentView
                         DataDoc={this.props.DataDocument}
-                        Document={this.props.Document} isTopMost={false} selectOnLoad={false}
+                        Document={this.props.Document} renderDepth={0} selectOnLoad={false}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument}
                         ScreenToLocalTransform={this.getTransform}
                         ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c6f003a81..05909c9f7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -199,7 +199,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             var dv = DocumentManager.Instance.getDocumentView(doc);
             return dv && SelectionManager.IsSelected(dv) ? true : false;
         });
-        if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
+        if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) {
             return;
         }
         e.stopPropagation();
@@ -303,7 +303,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
-            isTopMost: false,
+            renderDepth: this.props.renderDepth + 1,
             selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
@@ -404,7 +404,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get overlayView() {
         return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
-            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.overlayView;
@@ -416,7 +416,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
     @computed get backgroundView() {
         let props = this.props;
         return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
-            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+            renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.props.Document.backgroundLayout ? this.backgroundView : (null);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a4316c143..0da4888a1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -103,6 +103,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5530d5c01..c586885a3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -72,7 +72,7 @@ export interface DocumentViewProps {
     removeDocument?: (doc: Doc) => boolean;
     moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
-    isTopMost: boolean;
+    renderDepth: number;
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
@@ -120,7 +120,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
-    @computed get topMost(): boolean { return this.props.isTopMost; }
+    @computed get topMost(): boolean { return this.props.renderDepth === 0; }
     @computed get templates(): List<string> {
         let field = this.props.Document.templates;
         if (field && field instanceof List) {
@@ -268,7 +268,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.stopPropagation();
         let altKey = e.altKey;
         let ctrlKey = e.ctrlKey;
-        if (this._doubleTap && !this.props.isTopMost) {
+        if (this._doubleTap && !this.props.renderDepth) {
             this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab");
             SelectionManager.DeselectAll();
             this.props.Document.libraryBrush = false;
@@ -436,7 +436,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     addTemplate = (template: Template) => {
         this.templates.push(template.Layout);
-        this.templates = this.templates;
     }
 
     @action
@@ -447,7 +446,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 break;
             }
         }
-        this.templates = this.templates;
     }
 
     freezeNativeDimensions = (): void => {
@@ -549,7 +547,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         return (
-            <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
+            <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
                     outlineColor: "maroon",
@@ -565,7 +563,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     transform: `scale(${scaling}, ${scaling})`
                 }}
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
             >
                 {this.contents}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8879da55f..374a523cb 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -33,7 +33,7 @@ export interface FieldViewProps {
     DataDoc: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
-    isTopMost: boolean;
+    renderDepth: number;
     selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
     addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
@@ -97,7 +97,7 @@ export class FieldView extends React.Component<FieldViewProps> {
             //         ContentScaling={returnOne}
             //         PanelWidth={returnHundred}
             //         PanelHeight={returnHundred}
-            //         isTopMost={true} //TODO Why is this top most?
+            //         renderDepth={0} //TODO Why is renderDepth reset?
             //         selectOnLoad={false}
             //         focus={emptyFunction}
             //         isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3e03e7e75..3dda81db7 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -32,7 +32,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             fieldKey: this.props.keyName,
             isSelected: returnFalse,
             select: emptyFunction,
-            isTopMost: false,
+            renderDepth: 1,
             selectOnLoad: false,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b9e93b4da..10c66d318 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -492,7 +492,7 @@ interface IAnnotationProps {
 //                         <DocumentView Document={targetDoc}
 //                             ContainingCollectionView={undefined}
 //                             ScreenToLocalTransform={this.props.parent.props.parent.props.ScreenToLocalTransform}
-//                             isTopMost={false}
+//                             renderDepth={1} // what should renderDepth be?
 //                             ContentScaling={() => 1}
 //                             PanelWidth={() => NumCast(this.props.parent.props.parent.Document.nativeWidth)}
 //                             PanelHeight={() => NumCast(this.props.parent.props.parent.Document.nativeHeight)}
-- 
cgit v1.2.3-70-g09d2


From 4e00ff1c268f0c5453b448dd1810e14c47b37969 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 28 Jun 2019 16:27:38 -0400
Subject: test2

---
 src/client/views/nodes/DocumentContentsView.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 0da4888a1..240b21714 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -52,7 +52,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         const layout = Cast(layoutDoc[this.props.layoutKey], "string");
         if (layout === undefined) {
             return this.props.Document.data ?
-                "<FieldView {...props} fieldKey='data' />" :
+                "null" :
+                // "<FieldView {...props} fieldKey='data' />" :
                 KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : "");
         } else if (typeof layout === "string") {
             return layout;
-- 
cgit v1.2.3-70-g09d2


From 8ef971485492e3dc461cd93b2ae89e01b9995741 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 28 Jun 2019 16:57:46 -0400
Subject: undo tests

---
 src/client/views/nodes/DocumentContentsView.tsx | 3 +--
 src/client/views/nodes/KeyValueBox.tsx          | 1 -
 src/client/views/pdf/PDFViewer.tsx              | 3 +--
 src/new_fields/Doc.ts                           | 1 -
 4 files changed, 2 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 240b21714..0da4888a1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -52,8 +52,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         const layout = Cast(layoutDoc[this.props.layoutKey], "string");
         if (layout === undefined) {
             return this.props.Document.data ?
-                "null" :
-                // "<FieldView {...props} fieldKey='data' />" :
+                "<FieldView {...props} fieldKey='data' />" :
                 KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : "");
         } else if (typeof layout === "string") {
             return layout;
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index eee4ec670..0e798d291 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -207,7 +207,6 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     render() {
-        return null;
         let dividerDragger = this.splitPercentage === 0 ? (null) :
             <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
                 <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 380ba3c45..a645b0041 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
@@ -620,7 +620,6 @@ class Viewer extends React.Component<IViewerProps> {
     }
 
     render() {
-        trace();
         let compiled = this._script;
         return (
             <div ref={this._mainCont} style={{ pointerEvents: "all" }} onPointerDown={this.pointerDown}>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0340806ef..27dcfba08 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -281,7 +281,6 @@ export namespace Doc {
     }
 
     export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
-        return templateLayoutDoc;
         let resolvedDataDoc = (templateLayoutDoc !== dataDoc) ? dataDoc : undefined;
         if (!dataDoc || !(templateLayoutDoc && !(Cast(templateLayoutDoc.layout, Doc) instanceof Doc) && resolvedDataDoc && resolvedDataDoc !== templateLayoutDoc)) {
             return templateLayoutDoc;
-- 
cgit v1.2.3-70-g09d2


From 74e4909a77ac143ecdb1d038ad182aae9c710129 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 3 Jul 2019 22:15:25 -0400
Subject: implemented directory import routine

---
 src/client/documents/Documents.ts                  |  57 ++++++++-
 src/client/util/Import & Export/ImageImporter.tsx  |  67 +++++++++++
 src/client/util/Import & Export/ImportBox.tsx      | 134 +++++++++++++++++++++
 src/client/util/SelectionManager.ts                |   2 +-
 src/client/views/MainView.tsx                      |   3 +
 src/client/views/collections/CollectionSubView.tsx |  46 +------
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 7 files changed, 265 insertions(+), 47 deletions(-)
 create mode 100644 src/client/util/Import & Export/ImageImporter.tsx
 create mode 100644 src/client/util/Import & Export/ImportBox.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7d7a1f02a..5d637dd3a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -36,6 +36,7 @@ import { UndoManager } from "../util/UndoManager";
 import { RouteStore } from "../../server/RouteStore";
 import { LinkManager } from "../util/LinkManager";
 import { DocumentManager } from "../util/DocumentManager";
+import ImportBox from "../util/Import & Export/ImportBox";
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
@@ -51,7 +52,8 @@ export enum DocTypes {
     KVP = "kvp",
     VID = "video",
     AUDIO = "audio",
-    LINK = "link"
+    LINK = "link",
+    IMPORT = "import"
 }
 
 export interface DocumentOptions {
@@ -127,6 +129,7 @@ export namespace Docs {
     let audioProto: Doc;
     let pdfProto: Doc;
     let iconProto: Doc;
+    let importProto: Doc;
     // let linkProto: Doc;
     const textProtoId = "textProto";
     const histoProtoId = "histoProto";
@@ -138,6 +141,7 @@ export namespace Docs {
     const videoProtoId = "videoProto";
     const audioProtoId = "audioProto";
     const iconProtoId = "iconProto";
+    const importProtoId = "importProto";
     // const linkProtoId = "linkProto";
 
     export function initProtos(): Promise<void> {
@@ -152,6 +156,7 @@ export namespace Docs {
             audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
             pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
             iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
+            importProto = fields[importProtoId] as Doc || CreateImportPrototype();
         });
     }
 
@@ -174,6 +179,11 @@ export namespace Docs {
         return imageProto;
     }
 
+    function CreateImportPrototype(): Doc {
+        let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", ImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT });
+        return importProto;
+    }
+
     function CreateHistogramPrototype(): Doc {
         let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
             { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString(), type: DocTypes.HIST });
@@ -261,6 +271,10 @@ export namespace Docs {
         return CreateInstance(audioProto, new AudioField(new URL(url)), options);
     }
 
+    export function DirectoryImportDocument(options: DocumentOptions = {}) {
+        return CreateInstance(importProto, "", options);
+    }
+
     export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
         return CreateInstance(histoProto, new HistogramField(histoOp), options);
     }
@@ -333,6 +347,47 @@ export namespace Docs {
         return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
     }
 
+    export async function getDocumentFromType(type: string, path: string, options: DocumentOptions, addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean): Promise<Opt<Doc>> {
+        let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
+        if (type.indexOf("image") !== -1) {
+            ctor = Docs.ImageDocument;
+        }
+        if (type.indexOf("video") !== -1) {
+            ctor = Docs.VideoDocument;
+        }
+        if (type.indexOf("audio") !== -1) {
+            ctor = Docs.AudioDocument;
+        }
+        if (type.indexOf("pdf") !== -1) {
+            ctor = Docs.PdfDocument;
+            options.nativeWidth = 1200;
+        }
+        if (type.indexOf("excel") !== -1) {
+            ctor = Docs.DBDocument;
+            options.dropAction = "copy";
+        }
+        if (type.indexOf("html") !== -1) {
+            if (path.includes(window.location.hostname)) {
+                let s = path.split('/');
+                let id = s[s.length - 1];
+                DocServer.GetRefField(id).then(field => {
+                    if (field instanceof Doc) {
+                        let alias = Doc.MakeAlias(field);
+                        alias.x = options.x || 0;
+                        alias.y = options.y || 0;
+                        alias.width = options.width || 300;
+                        alias.height = options.height || options.width || 300;
+                        addDocument && addDocument(alias, false);
+                    }
+                });
+                return undefined;
+            }
+            ctor = Docs.WebDocument;
+            options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+        }
+        return ctor ? ctor(path, options) : undefined;
+    }
+
     export function CaptionDocument(doc: Doc) {
         const captionDoc = Doc.MakeAlias(doc);
         captionDoc.overlayLayout = FixedCaption();
diff --git a/src/client/util/Import & Export/ImageImporter.tsx b/src/client/util/Import & Export/ImageImporter.tsx
new file mode 100644
index 000000000..d664f6487
--- /dev/null
+++ b/src/client/util/Import & Export/ImageImporter.tsx	
@@ -0,0 +1,67 @@
+import "fs";
+import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
+import { DocServer } from "../../DocServer";
+import { RouteStore } from "../../../server/RouteStore";
+import { action } from "mobx";
+import { Docs } from "../../documents/Documents";
+import { FieldViewProps } from "../../views/nodes/FieldView";
+
+interface ImageImporterProps {
+    addSchema: (imageDocs: Doc[]) => void;
+}
+
+export default class BulkImporter extends React.Component<FieldViewProps> {
+    private selector = React.createRef<HTMLInputElement>();
+
+    handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
+        let promises: Promise<void>[] = [];
+        let docs: Doc[] = [];
+
+        let files = e.target.files;
+        if (!files) return;
+
+        for (let i = 0; i < files.length; i++) {
+            let target = files.item(i);
+
+            if (target === null) {
+                continue;
+            }
+
+            let type = target.type;
+            let formData = new FormData();
+            formData.append('file', target);
+            let dropFileName = target ? target.name : "-empty-";
+
+            let prom = fetch(DocServer.prepend(RouteStore.upload), {
+                method: 'POST',
+                body: formData
+            }).then(async (res: Response) => {
+                (await res.json()).map(action((file: any) => {
+                    let path = window.location.origin + file;
+                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
+                    docPromise.then(doc => doc && docs.push(doc));
+                }));
+            });
+            promises.push(prom);
+        }
+
+        await Promise.all(promises);
+
+        let parent = Docs.SchemaDocument(["title", "data"], docs, { width: 300, height: 300, title: "Bulk Import from Directory" });
+    }
+
+    componentDidMount() {
+        this.selector.current!.setAttribute("directory", "true");
+        this.selector.current!.setAttribute("webkitdirectory", "true");
+    }
+
+    render() {
+        return (
+            <div>
+                <input ref={this.selector} name={"selector"} onChange={this.handleSelection} type="file" style={{ position: "absolute" }} />
+            </div>
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/util/Import & Export/ImportBox.tsx b/src/client/util/Import & Export/ImportBox.tsx
new file mode 100644
index 000000000..630911710
--- /dev/null
+++ b/src/client/util/Import & Export/ImportBox.tsx	
@@ -0,0 +1,134 @@
+import "fs";
+import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
+import { DocServer } from "../../DocServer";
+import { RouteStore } from "../../../server/RouteStore";
+import { action, observable } from "mobx";
+import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
+import Measure, { ContentRect } from "react-measure";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faArrowUp } from '@fortawesome/free-solid-svg-icons';
+import { Docs, DocumentOptions } from "../../documents/Documents";
+
+interface ImageImporterProps {
+    addSchema: (imageDocs: Doc[]) => void;
+}
+
+export default class ImportBox extends React.Component<FieldViewProps> {
+    @observable private top = 0;
+    @observable private left = 0;
+    private dimensions = 50;
+
+    constructor(props: FieldViewProps) {
+        super(props);
+        library.add(faArrowUp);
+    }
+
+    public static LayoutString() { return FieldView.LayoutString(ImportBox); }
+
+    private selector = React.createRef<HTMLInputElement>();
+
+    handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
+        let promises: Promise<void>[] = [];
+        let docs: Doc[] = [];
+
+        let files = e.target.files;
+        if (!files || files.length === 0) return;
+
+        let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
+
+        for (let i = 0; i < files.length; i++) {
+            let uploaded_file = files.item(i);
+
+            if (!uploaded_file) {
+                continue;
+            }
+
+            let formData = new FormData();
+            formData.append('file', uploaded_file);
+            let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
+            let type = uploaded_file.type;
+
+            let prom = fetch(DocServer.prepend(RouteStore.upload), {
+                method: 'POST',
+                body: formData
+            }).then(async (res: Response) => {
+                (await res.json()).map(action((file: any) => {
+                    let path = DocServer.prepend(file);
+                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
+                    docPromise.then(doc => doc && docs.push(doc));
+                }));
+            });
+            promises.push(prom);
+        }
+
+        await Promise.all(promises);
+
+        let doc = this.props.Document;
+        let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") };
+        let parent = this.props.ContainingCollectionView;
+        if (parent) {
+            let importContainer = Docs.StackingDocument(docs, options);
+            Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
+            this.props.removeDocument && this.props.removeDocument(doc);
+        }
+    }
+
+    componentDidMount() {
+        this.selector.current!.setAttribute("directory", "true");
+        this.selector.current!.setAttribute("webkitdirectory", "true");
+    }
+
+    @action
+    preserveCentering = (rect: ContentRect) => {
+        let bounds = rect.offset!;
+        if (bounds.width === 0 || bounds.height === 0) {
+            return;
+        }
+        let offset = this.dimensions / 2;
+        this.left = bounds.width / 2 - offset;
+        this.top = bounds.height / 2 - offset;
+    }
+
+    render() {
+        let dimensions = 50;
+        return (
+            <Measure offset onResize={this.preserveCentering}>
+                {({ measureRef }) =>
+                    <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
+                        <input
+                            id={"selector"}
+                            ref={this.selector}
+                            name={"selector"}
+                            onChange={this.handleSelection}
+                            type="file"
+                            style={{
+                                position: "absolute",
+                                display: "none"
+                            }} />
+                        <label htmlFor={"selector"}>
+                            <div style={{
+                                width: dimensions,
+                                height: dimensions,
+                                borderRadius: "50%",
+                                background: "black",
+                                position: "absolute",
+                                left: this.left,
+                                top: this.top
+                            }} />
+                            <div style={{
+                                position: "absolute",
+                                left: this.left + 12.5,
+                                top: this.top + 11
+                            }}>
+                                <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
+                            </div>
+                        </label>
+                    </div>
+                }
+            </Measure>
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 3c396362e..9efef888d 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -53,7 +53,7 @@ export namespace SelectionManager {
             stored.length > 0 && (targetColor = stored);
         }
         InkingControl.Instance.updateSelectedColor(targetColor);
-    });
+    }, { fireImmediately: true });
 
     export function DeselectDoc(docView: DocumentView): void {
         manager.DeselectDoc(docView);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ab9fe0118..e38fd3c8b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -38,6 +38,7 @@ import PDFMenu from './pdf/PDFMenu';
 import { InkTool } from '../../new_fields/InkField';
 import _ from "lodash";
 import KeyManager from './GlobalKeyHandler';
+import BulkImporter from '../util/Import & Export/ImageImporter';
 
 @observer
 export class MainView extends React.Component {
@@ -351,11 +352,13 @@ export class MainView extends React.Component {
         let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
         let addTreeNode = action(() => CurrentUserUtils.UserDocument);
         let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+        let addImportCollectionNode = action(() => Docs.DirectoryImportDocument({ title: "Directory Import", width: 150, height: 150 }));
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
             [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
             [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
+            [React.createRef<HTMLDivElement>(), "arrow-up", "Import Directory", addImportCollectionNode],
         ];
 
         return < div id="add-nodes-menu" >
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3b3bbdbd9..e886ad84a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -102,47 +102,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             return false;
         }
 
-        protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
-            let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
-            if (type.indexOf("image") !== -1) {
-                ctor = Docs.ImageDocument;
-            }
-            if (type.indexOf("video") !== -1) {
-                ctor = Docs.VideoDocument;
-            }
-            if (type.indexOf("audio") !== -1) {
-                ctor = Docs.AudioDocument;
-            }
-            if (type.indexOf("pdf") !== -1) {
-                ctor = Docs.PdfDocument;
-                options.nativeWidth = 1200;
-            }
-            if (type.indexOf("excel") !== -1) {
-                ctor = Docs.DBDocument;
-                options.dropAction = "copy";
-            }
-            if (type.indexOf("html") !== -1) {
-                if (path.includes(window.location.hostname)) {
-                    let s = path.split('/');
-                    let id = s[s.length - 1];
-                    DocServer.GetRefField(id).then(field => {
-                        if (field instanceof Doc) {
-                            let alias = Doc.MakeAlias(field);
-                            alias.x = options.x || 0;
-                            alias.y = options.y || 0;
-                            alias.width = options.width || 300;
-                            alias.height = options.height || options.width || 300;
-                            this.props.addDocument(alias, false);
-                        }
-                    });
-                    return undefined;
-                }
-                ctor = Docs.WebDocument;
-                options = { height: options.width, ...options, title: path, nativeWidth: undefined };
-            }
-            return ctor ? ctor(path, options) : undefined;
-        }
-
         @undoBatch
         @action
         protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
@@ -223,7 +182,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                         .then(result => {
                             let type = result["content-type"];
                             if (type) {
-                                this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+                                Docs.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }, this.props.addDocument)
                                     .then(doc => doc && this.props.addDocument(doc, false));
                             }
                         });
@@ -245,8 +204,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     }).then(async (res: Response) => {
                         (await res.json()).map(action((file: any) => {
                             let path = window.location.origin + file;
-                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
-
+                            let docPromise = Docs.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }, this.props.addDocument);
                             docPromise.then(doc => doc && this.props.addDocument(doc));
                         }));
                     });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 0da4888a1..e9fec1588 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,6 +24,7 @@ import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { Doc } from "../../../new_fields/Doc";
+import ImportBox from "../../util/Import & Export/ImportBox";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -106,7 +107,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
-            components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, ImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
             jsx={this.finalLayout}
             showWarnings={true}
-- 
cgit v1.2.3-70-g09d2


From aedd283fb9f9eff4145e27658bc6647982256032 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 5 Jul 2019 00:34:19 -0400
Subject: beginning implementation of key value tags for all imported documents

---
 src/client/documents/Documents.ts                  |  12 +-
 .../util/Import & Export/DirectoryImportBox.scss   |   0
 .../util/Import & Export/DirectoryImportBox.tsx    | 181 +++++++++++++++++++++
 src/client/util/Import & Export/ImageImporter.tsx  |  67 --------
 src/client/util/Import & Export/ImportBox.tsx      | 134 ---------------
 src/client/util/request-image-size.js              |   6 +-
 src/client/views/MainView.tsx                      |   1 -
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 10 files changed, 198 insertions(+), 215 deletions(-)
 create mode 100644 src/client/util/Import & Export/DirectoryImportBox.scss
 create mode 100644 src/client/util/Import & Export/DirectoryImportBox.tsx
 delete mode 100644 src/client/util/Import & Export/ImageImporter.tsx
 delete mode 100644 src/client/util/Import & Export/ImportBox.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5d637dd3a..26b5498a2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -36,7 +36,7 @@ import { UndoManager } from "../util/UndoManager";
 import { RouteStore } from "../../server/RouteStore";
 import { LinkManager } from "../util/LinkManager";
 import { DocumentManager } from "../util/DocumentManager";
-import ImportBox from "../util/Import & Export/ImportBox";
+import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox";
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
@@ -180,7 +180,7 @@ export namespace Docs {
     }
 
     function CreateImportPrototype(): Doc {
-        let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", ImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT });
+        let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", DirectoryImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT });
         return importProto;
     }
 
@@ -347,7 +347,7 @@ export namespace Docs {
         return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
     }
 
-    export async function getDocumentFromType(type: string, path: string, options: DocumentOptions, addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean): Promise<Opt<Doc>> {
+    export async function getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
         let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
         if (type.indexOf("image") !== -1) {
             ctor = Docs.ImageDocument;
@@ -370,17 +370,17 @@ export namespace Docs {
             if (path.includes(window.location.hostname)) {
                 let s = path.split('/');
                 let id = s[s.length - 1];
-                DocServer.GetRefField(id).then(field => {
+                return DocServer.GetRefField(id).then(field => {
                     if (field instanceof Doc) {
                         let alias = Doc.MakeAlias(field);
                         alias.x = options.x || 0;
                         alias.y = options.y || 0;
                         alias.width = options.width || 300;
                         alias.height = options.height || options.width || 300;
-                        addDocument && addDocument(alias, false);
+                        return alias;
                     }
+                    return undefined;
                 });
-                return undefined;
             }
             ctor = Docs.WebDocument;
             options = { height: options.width, ...options, title: path, nativeWidth: undefined };
diff --git a/src/client/util/Import & Export/DirectoryImportBox.scss b/src/client/util/Import & Export/DirectoryImportBox.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
new file mode 100644
index 000000000..2d77f6ae6
--- /dev/null
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -0,0 +1,181 @@
+import "fs";
+import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
+import { DocServer } from "../../DocServer";
+import { RouteStore } from "../../../server/RouteStore";
+import { action, observable, runInAction } from "mobx";
+import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
+import Measure, { ContentRect } from "react-measure";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faArrowUp, faTag, faFileExcel } from '@fortawesome/free-solid-svg-icons';
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { EditableView } from "../../views/EditableView";
+
+export default class DirectoryImportBox extends React.Component<FieldViewProps> {
+    private selector = React.createRef<HTMLInputElement>();
+    @observable private top = 0;
+    @observable private left = 0;
+    private dimensions = 50;
+
+    @observable private key = "Key";
+    @observable private value = "Value";
+
+    public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); }
+
+    constructor(props: FieldViewProps) {
+        super(props);
+        library.add(faArrowUp, faTag);
+    }
+
+    updateKey = (newKey: string) => {
+        runInAction(() => this.key = newKey);
+        console.log("KEY ", this.key);
+        return true;
+    }
+
+    updateValue = (newValue: string) => {
+        runInAction(() => this.value = newValue);
+        console.log("VALUE ", this.value);
+        return true;
+    }
+
+    handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
+        let promises: Promise<void>[] = [];
+        let docs: Doc[] = [];
+
+        let files = e.target.files;
+        if (!files || files.length === 0) return;
+
+        let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
+
+        for (let i = 0; i < files.length; i++) {
+            let uploaded_file = files.item(i);
+
+            if (!uploaded_file) {
+                continue;
+            }
+
+            let formData = new FormData();
+            formData.append('file', uploaded_file);
+            let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
+            let type = uploaded_file.type;
+
+            let prom = fetch(DocServer.prepend(RouteStore.upload), {
+                method: 'POST',
+                body: formData
+            }).then(async (res: Response) => {
+                (await res.json()).map(action((file: any) => {
+                    let path = DocServer.prepend(file);
+                    console.log(path);
+                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
+                    docPromise.then(doc => doc && docs.push(doc));
+                }));
+            });
+            promises.push(prom);
+        }
+
+        await Promise.all(promises);
+
+        let doc = this.props.Document;
+        let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") };
+        let parent = this.props.ContainingCollectionView;
+        if (parent) {
+            let importContainer = Docs.StackingDocument(docs, options);
+            Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
+            this.props.removeDocument && this.props.removeDocument(doc);
+        }
+    }
+
+    componentDidMount() {
+        this.selector.current!.setAttribute("directory", "");
+        this.selector.current!.setAttribute("webkitdirectory", "");
+    }
+
+    @action
+    preserveCentering = (rect: ContentRect) => {
+        let bounds = rect.offset!;
+        if (bounds.width === 0 || bounds.height === 0) {
+            return;
+        }
+        let offset = this.dimensions / 2;
+        this.left = bounds.width / 2 - offset;
+        this.top = bounds.height / 2 - offset;
+    }
+
+    render() {
+        let dimensions = 50;
+        let keyValueStyle = { paddingLeft: 5, width: "50%" };
+        return (
+            <Measure offset onResize={this.preserveCentering}>
+                {({ measureRef }) =>
+                    <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
+                        <input
+                            id={"selector"}
+                            ref={this.selector}
+                            name={"selector"}
+                            onChange={this.handleSelection}
+                            type="file"
+                            style={{
+                                position: "absolute",
+                                display: "none"
+                            }} />
+                        <label htmlFor={"selector"}>
+                            <div style={{
+                                width: dimensions,
+                                height: dimensions,
+                                borderRadius: "50%",
+                                background: "black",
+                                position: "absolute",
+                                left: this.left,
+                                top: this.top
+                            }} />
+                            <div style={{
+                                position: "absolute",
+                                left: this.left + 12.5,
+                                top: this.top + 11
+                            }}>
+                                <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
+                            </div>
+                        </label>
+                        <div style={{
+                            position: "absolute",
+                            top: 5,
+                            right: 5,
+                            borderRadius: "50%",
+                            width: 25,
+                            height: 25,
+                            background: "black"
+                        }} />
+                        <div style={{
+                            position: "absolute",
+                            right: 9.5,
+                            top: 11
+                        }}>
+                            <FontAwesomeIcon icon={faTag} color="#FFFFFF" size={"1x"} />
+                        </div>
+                        <div style={{ display: "flex", flexDirection: "row", borderBottom: "1px solid black", paddingBottom: 5 }} >
+                            <div className={"key_container"} style={keyValueStyle}>
+                                <EditableView
+                                    contents={this.key}
+                                    SetValue={this.updateKey}
+                                    GetValue={() => this.key}
+                                    oneLine={true}
+                                />
+                            </div>
+                            <div className={"value_container"} style={keyValueStyle}>
+                                <EditableView
+                                    contents={this.value}
+                                    SetValue={this.updateValue}
+                                    GetValue={() => this.value}
+                                    oneLine={true}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                }
+            </Measure>
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/util/Import & Export/ImageImporter.tsx b/src/client/util/Import & Export/ImageImporter.tsx
deleted file mode 100644
index d664f6487..000000000
--- a/src/client/util/Import & Export/ImageImporter.tsx	
+++ /dev/null
@@ -1,67 +0,0 @@
-import "fs";
-import React = require("react");
-import { Doc } from "../../../new_fields/Doc";
-import { DocServer } from "../../DocServer";
-import { RouteStore } from "../../../server/RouteStore";
-import { action } from "mobx";
-import { Docs } from "../../documents/Documents";
-import { FieldViewProps } from "../../views/nodes/FieldView";
-
-interface ImageImporterProps {
-    addSchema: (imageDocs: Doc[]) => void;
-}
-
-export default class BulkImporter extends React.Component<FieldViewProps> {
-    private selector = React.createRef<HTMLInputElement>();
-
-    handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
-        let promises: Promise<void>[] = [];
-        let docs: Doc[] = [];
-
-        let files = e.target.files;
-        if (!files) return;
-
-        for (let i = 0; i < files.length; i++) {
-            let target = files.item(i);
-
-            if (target === null) {
-                continue;
-            }
-
-            let type = target.type;
-            let formData = new FormData();
-            formData.append('file', target);
-            let dropFileName = target ? target.name : "-empty-";
-
-            let prom = fetch(DocServer.prepend(RouteStore.upload), {
-                method: 'POST',
-                body: formData
-            }).then(async (res: Response) => {
-                (await res.json()).map(action((file: any) => {
-                    let path = window.location.origin + file;
-                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
-                    docPromise.then(doc => doc && docs.push(doc));
-                }));
-            });
-            promises.push(prom);
-        }
-
-        await Promise.all(promises);
-
-        let parent = Docs.SchemaDocument(["title", "data"], docs, { width: 300, height: 300, title: "Bulk Import from Directory" });
-    }
-
-    componentDidMount() {
-        this.selector.current!.setAttribute("directory", "true");
-        this.selector.current!.setAttribute("webkitdirectory", "true");
-    }
-
-    render() {
-        return (
-            <div>
-                <input ref={this.selector} name={"selector"} onChange={this.handleSelection} type="file" style={{ position: "absolute" }} />
-            </div>
-        );
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/util/Import & Export/ImportBox.tsx b/src/client/util/Import & Export/ImportBox.tsx
deleted file mode 100644
index 630911710..000000000
--- a/src/client/util/Import & Export/ImportBox.tsx	
+++ /dev/null
@@ -1,134 +0,0 @@
-import "fs";
-import React = require("react");
-import { Doc } from "../../../new_fields/Doc";
-import { DocServer } from "../../DocServer";
-import { RouteStore } from "../../../server/RouteStore";
-import { action, observable } from "mobx";
-import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
-import Measure, { ContentRect } from "react-measure";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faArrowUp } from '@fortawesome/free-solid-svg-icons';
-import { Docs, DocumentOptions } from "../../documents/Documents";
-
-interface ImageImporterProps {
-    addSchema: (imageDocs: Doc[]) => void;
-}
-
-export default class ImportBox extends React.Component<FieldViewProps> {
-    @observable private top = 0;
-    @observable private left = 0;
-    private dimensions = 50;
-
-    constructor(props: FieldViewProps) {
-        super(props);
-        library.add(faArrowUp);
-    }
-
-    public static LayoutString() { return FieldView.LayoutString(ImportBox); }
-
-    private selector = React.createRef<HTMLInputElement>();
-
-    handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
-        let promises: Promise<void>[] = [];
-        let docs: Doc[] = [];
-
-        let files = e.target.files;
-        if (!files || files.length === 0) return;
-
-        let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
-
-        for (let i = 0; i < files.length; i++) {
-            let uploaded_file = files.item(i);
-
-            if (!uploaded_file) {
-                continue;
-            }
-
-            let formData = new FormData();
-            formData.append('file', uploaded_file);
-            let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
-            let type = uploaded_file.type;
-
-            let prom = fetch(DocServer.prepend(RouteStore.upload), {
-                method: 'POST',
-                body: formData
-            }).then(async (res: Response) => {
-                (await res.json()).map(action((file: any) => {
-                    let path = DocServer.prepend(file);
-                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
-                    docPromise.then(doc => doc && docs.push(doc));
-                }));
-            });
-            promises.push(prom);
-        }
-
-        await Promise.all(promises);
-
-        let doc = this.props.Document;
-        let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") };
-        let parent = this.props.ContainingCollectionView;
-        if (parent) {
-            let importContainer = Docs.StackingDocument(docs, options);
-            Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
-            this.props.removeDocument && this.props.removeDocument(doc);
-        }
-    }
-
-    componentDidMount() {
-        this.selector.current!.setAttribute("directory", "true");
-        this.selector.current!.setAttribute("webkitdirectory", "true");
-    }
-
-    @action
-    preserveCentering = (rect: ContentRect) => {
-        let bounds = rect.offset!;
-        if (bounds.width === 0 || bounds.height === 0) {
-            return;
-        }
-        let offset = this.dimensions / 2;
-        this.left = bounds.width / 2 - offset;
-        this.top = bounds.height / 2 - offset;
-    }
-
-    render() {
-        let dimensions = 50;
-        return (
-            <Measure offset onResize={this.preserveCentering}>
-                {({ measureRef }) =>
-                    <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
-                        <input
-                            id={"selector"}
-                            ref={this.selector}
-                            name={"selector"}
-                            onChange={this.handleSelection}
-                            type="file"
-                            style={{
-                                position: "absolute",
-                                display: "none"
-                            }} />
-                        <label htmlFor={"selector"}>
-                            <div style={{
-                                width: dimensions,
-                                height: dimensions,
-                                borderRadius: "50%",
-                                background: "black",
-                                position: "absolute",
-                                left: this.left,
-                                top: this.top
-                            }} />
-                            <div style={{
-                                position: "absolute",
-                                left: this.left + 12.5,
-                                top: this.top + 11
-                            }}>
-                                <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
-                            </div>
-                        </label>
-                    </div>
-                }
-            </Measure>
-        );
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js
index 0f9328872..27605d167 100644
--- a/src/client/util/request-image-size.js
+++ b/src/client/util/request-image-size.js
@@ -21,7 +21,9 @@ module.exports = function requestImageSize(options) {
     if (options && typeof options === 'object') {
         opts = Object.assign(options, opts);
     } else if (options && typeof options === 'string') {
-        opts = Object.assign({ uri: options }, opts);
+        opts = Object.assign({
+            uri: options
+        }, opts);
     } else {
         return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
     }
@@ -70,4 +72,4 @@ module.exports = function requestImageSize(options) {
 
         req.on('error', err => reject(err));
     });
-};
+};
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e38fd3c8b..3d059b2f3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -38,7 +38,6 @@ import PDFMenu from './pdf/PDFMenu';
 import { InkTool } from '../../new_fields/InkField';
 import _ from "lodash";
 import KeyManager from './GlobalKeyHandler';
-import BulkImporter from '../util/Import & Export/ImageImporter';
 
 @observer
 export class MainView extends React.Component {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e886ad84a..8cb2e1beb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -182,7 +182,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                         .then(result => {
                             let type = result["content-type"];
                             if (type) {
-                                Docs.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }, this.props.addDocument)
+                                Docs.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
                                     .then(doc => doc && this.props.addDocument(doc, false));
                             }
                         });
@@ -204,7 +204,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     }).then(async (res: Response) => {
                         (await res.json()).map(action((file: any) => {
                             let path = window.location.origin + file;
-                            let docPromise = Docs.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }, this.props.addDocument);
+                            let docPromise = Docs.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
                             docPromise.then(doc => doc && this.props.addDocument(doc));
                         }));
                     });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e9fec1588..0bfb2954f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,7 +24,7 @@ import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { Doc } from "../../../new_fields/Doc";
-import ImportBox from "../../util/Import & Export/ImportBox";
+import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -107,7 +107,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
-            components={{ FormattedTextBox, ImageBox, IconBox, ImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
             jsx={this.finalLayout}
             showWarnings={true}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 06bf65f73..88c122b61 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -213,7 +213,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                     }), 0);
                 }
             })
-            .catch((err: any) => console.log(err));
+            .catch((err: any) => {
+                console.log(err);
+            });
     }
 
     render() {
-- 
cgit v1.2.3-70-g09d2


From 336266551d363df8c4958d485170846b478f1787 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 5 Jul 2019 11:54:39 -0400
Subject: fixed templates (old style) a bit.

---
 deploy/assets/downarrow.png                              | Bin 0 -> 3363 bytes
 src/client/views/Templates.tsx                           |   3 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx        |   4 ++--
 src/client/views/nodes/DocumentContentsView.tsx          |   5 ++++-
 4 files changed, 7 insertions(+), 5 deletions(-)
 create mode 100644 deploy/assets/downarrow.png

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

diff --git a/deploy/assets/downarrow.png b/deploy/assets/downarrow.png
new file mode 100644
index 000000000..3c59ff5b1
Binary files /dev/null and b/deploy/assets/downarrow.png differ
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 4843a70a6..236704fa2 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -69,8 +69,7 @@ export namespace Templates {
         `< div >
         <div style="height:100%; width:100%;position:absolute;">{layout}</div>
         <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
-            <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg=="
-                width="15px" height="15px" />
+            <img id="isExpander" src="/assets/downarrow.png" width="15px" height="15px" />
         </div>
         </div > `
     );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b7d462e5..7c0591600 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -468,8 +468,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             description: "Add freeform arrangement",
             event: () => {
                 let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
-                    const docs = DocListCast(this.Document[this.props.fieldKey]);
-                    docs.map(d => d.transition = "transform 1s");
                     let overlayDisposer: () => void;
                     const script = this.Document[key];
                     let originalText: string | undefined = undefined;
@@ -484,6 +482,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             onError(script.errors.map(error => error.messageText).join("\n"));
                             return;
                         }
+                        const docs = DocListCast(this.Document[this.props.fieldKey]);
+                        docs.map(d => d.transition = "transform 1s");
                         this.Document[key] = new ScriptField(script);
                         overlayDisposer();
                         setTimeout(() => docs.map(d => d.transition = undefined), 1200);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 0da4888a1..51d0c83cd 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,6 +24,7 @@ import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { Doc } from "../../../new_fields/Doc";
+import { CollectionViewType } from "../collections/CollectionBaseView";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -82,7 +83,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         // by checking the layoutKey.  This should probably be moved into
         // a prop so that the overlay can explicitly turn off templates.
         if ((this.props.layoutKey === "overlayLayout" && StrCast(this.props.Document.layout).indexOf("CollectionView") !== -1) ||
-            (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
+            (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1) ||
+            (this.props.layoutKey === "layout" && NumCast(this.props.Document.viewType)) !== CollectionViewType.Freeform) {
             this.templates.forEach(template => {
                 let self = this;
                 // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
@@ -103,6 +105,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        console.log(this.props.Document.title);
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
-- 
cgit v1.2.3-70-g09d2


From d27eea0db5e5fc3de0b5ee0e58fc2db3377c4131 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 5 Jul 2019 14:53:38 -0400
Subject: Added some more search util functions

---
 src/client/util/SearchUtil.ts                   | 33 +++++++++++++++++++++----
 src/client/views/nodes/DocumentContentsView.tsx |  1 -
 2 files changed, 28 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ac2eb72e7..338628960 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -29,11 +29,16 @@ export namespace SearchUtil {
         return { docs, numFound };
     }
 
-    export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]> {
-        const proto = await Doc.GetT(doc, "proto", Doc, true);
-        const protoId = (proto || doc)[Id];
-        const result = await Search(`proto_i:"${protoId}"`, true);
-        return result.docs;
+    export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]>;
+    export async function GetAliasesOfDocument(doc: Doc, returnDocs: false): Promise<string[]>;
+    export async function GetAliasesOfDocument(doc: Doc, returnDocs = true): Promise<Doc[] | string[]> {
+        const proto = Doc.GetProto(doc);
+        const protoId = proto[Id];
+        if (returnDocs) {
+            return (await Search(`proto_i:"${protoId}"`, returnDocs)).docs;
+        } else {
+            return (await Search(`proto_i:"${protoId}"`, returnDocs)).ids;
+        }
         // return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
     }
 
@@ -41,4 +46,22 @@ export namespace SearchUtil {
         const results = await Search(`proto_i:"${doc[Id]}"`, true);
         return results.docs;
     }
+
+    export async function GetContextsOfDocument(doc: Doc): Promise<{ contexts: Doc[], aliasContexts: Doc[] }> {
+        const docContexts = (await Search(`data_l:"${doc[Id]}"`, true)).docs;
+        const aliases = await GetAliasesOfDocument(doc, false);
+        const aliasContexts = (await Promise.all(aliases.map(doc => Search(`data_l:"${doc}"`, true))));
+        const contexts = { contexts: docContexts, aliasContexts: [] as Doc[] };
+        aliasContexts.forEach(result => contexts.aliasContexts.push(...result.docs));
+        return contexts;
+    }
+
+    export async function GetContextIdsOfDocument(doc: Doc): Promise<{ contexts: string[], aliasContexts: string[] }> {
+        const docContexts = (await Search(`data_l:"${doc[Id]}"`, false)).ids;
+        const aliases = await GetAliasesOfDocument(doc, false);
+        const aliasContexts = (await Promise.all(aliases.map(doc => Search(`data_l:"${doc}"`, false))));
+        const contexts = { contexts: docContexts, aliasContexts: [] as string[] };
+        aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids));
+        return contexts;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 51d0c83cd..56a14e26e 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -105,7 +105,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
-        console.log(this.props.Document.title);
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
-- 
cgit v1.2.3-70-g09d2


From bc4e7ceb8a738b890344c4564ab0d97e0a10293d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 9 Jul 2019 10:57:58 -0400
Subject: fixed some template issues, added text templates.

---
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 src/client/views/nodes/DocumentContentsView.tsx    | 27 +++++++++---
 src/client/views/nodes/DocumentView.tsx            | 15 ++++++-
 src/client/views/nodes/FormattedTextBox.tsx        | 48 +++++++++++++---------
 5 files changed, 65 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 48b89ff4e..d6cf793ab 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -357,7 +357,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     onRadiusMove = (e: PointerEvent): void => {
         let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
-        SelectionManager.SelectedDocuments().map(dv => Doc.GetProto(dv.props.Document).borderRounding = `${Math.min(100, dist)}%`);
+        SelectionManager.SelectedDocuments().map(dv => dv.props.Document.borderRounding = Doc.GetProto(dv.props.Document).borderRounding = `${Math.min(100, dist)}%`);
         e.stopPropagation();
         e.preventDefault();
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 1c00687ed..b09538d1a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -7,6 +7,7 @@ import { DocComponent } from "../DocComponent";
 import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     x?: number;
@@ -70,7 +71,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
-        let br = StrCast(this.props.Document.borderRounding);
+        let br = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.borderRounding : this.props.Document.borderRounding);
         if (br.endsWith("%")) {
             let percent = Number(br.substr(0, br.length - 1)) / 100;
             let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight));
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 56a14e26e..745520bc4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -49,12 +49,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     hideOnLeave?: boolean
 }> {
     @computed get layout(): string {
-        let layoutDoc = this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
-        const layout = Cast(layoutDoc[this.props.layoutKey], "string");
+        const layout = Cast(this.layoutDoc[this.props.layoutKey], "string");
         if (layout === undefined) {
             return this.props.Document.data ?
                 "<FieldView {...props} fieldKey='data' />" :
-                KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : "");
+                KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : "");
         } else if (typeof layout === "string") {
             return layout;
         } else {
@@ -62,8 +61,23 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         }
     }
 
-    CreateBindings(layoutDoc?: Doc): JsxBindings {
-        return { props: { ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit, Document: layoutDoc } };
+    get dataDoc() {
+        if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+            // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
+            // has a template layout document, then we will render the template layout but use 
+            // this document as the data document for the layout.
+            return this.props.Document;
+        }
+        return this.props.DataDoc
+    }
+    get layoutDoc() {
+        // if this document's layout field contains a document (ie, a rendering template), then we will use that
+        // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
+        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+    }
+
+    CreateBindings(): JsxBindings {
+        return { props: { ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit, Document: this.layoutDoc, DataDoc: this.dataDoc } };
     }
 
     @computed get templates(): List<string> {
@@ -105,11 +119,12 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        let self = this;
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
             components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
-            bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
+            bindings={this.CreateBindings()}
             jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d027ed516..47ec3b5a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -218,7 +218,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.stopPropagation();
     }
 
-    get dataDoc() { return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; }
+    get dataDoc() {
+        if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+            // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
+            // has a template layout document, then we will render the template layout but use 
+            // this document as the data document for the layout.
+            return this.props.Document;
+        }
+        return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
+    }
     startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
         if (this._mainCont.current) {
             let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
@@ -585,20 +593,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
     @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
     @computed get contents() {
-        return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.props.selectOnLoad} layoutKey={"layout"} />);
+        return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.props.selectOnLoad} layoutKey={"layout"} DataDoc={this.dataDoc} />);
     }
 
     render() {
         if (this.Document.hidden) {
             return null;
         }
+        let self = this;
         let backgroundColor = this.props.Document.layout instanceof Doc ? StrCast(this.props.Document.layout.backgroundColor) : this.Document.backgroundColor;
+        let foregroundColor = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.color : this.props.Document.color);
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
+                    color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
                     outlineWidth: BoolCast(this.props.Document.libraryBrush) && !StrCast(this.props.Document.borderRounding) ?
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bf6f4c764..68a4372d5 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -99,7 +99,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @undoBatch
     public setFontColor(color: string) {
+        let self = this;
         if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false;
+        if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) {
+            this.props.Document.color = color;
+        }
         let colorMark = this._editorView!.state.schema.mark(this._editorView!.state.schema.marks.pFontColor, { color: color });
         this._editorView!.dispatch(this._editorView!.state.tr.addMark(this._editorView!.state.selection.from,
             this._editorView!.state.selection.to, colorMark));
@@ -160,25 +164,28 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else {
             if (de.data instanceof DragManager.DocumentDragData) {
-                let ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
-                if (!ldocs) {
-                    this.dataDoc.subBulletDocs = new List<Doc>([]);
-                }
-                ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
-                if (!ldocs) return;
-                if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) {
-                    ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.dataDoc.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
-                    this.props.addDocument && this.props.addDocument(ldocs[0] as Doc);
-                    this.props.Document.templates = new List<string>([Templates.Bullet.Layout]);
-                    this.props.Document.isBullet = true;
-                }
-                let stackDoc = (ldocs[0] as Doc);
-                if (de.data.moveDocument) {
-                    de.data.moveDocument(de.data.draggedDocuments[0], stackDoc, (doc) => {
-                        Cast(stackDoc.data, listSpec(Doc))!.push(doc);
-                        return true;
-                    });
-                }
+                this.props.Document.layout = de.data.draggedDocuments[0];
+                de.data.draggedDocuments[0].isTemplate = true;
+                e.stopPropagation();
+                // let ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
+                // if (!ldocs) {
+                //     this.dataDoc.subBulletDocs = new List<Doc>([]);
+                // }
+                // ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
+                // if (!ldocs) return;
+                // if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) {
+                //     ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.dataDoc.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
+                //     this.props.addDocument && this.props.addDocument(ldocs[0] as Doc);
+                //     this.props.Document.templates = new List<string>([Templates.Bullet.Layout]);
+                //     this.props.Document.isBullet = true;
+                // }
+                // let stackDoc = (ldocs[0] as Doc);
+                // if (de.data.moveDocument) {
+                //     de.data.moveDocument(de.data.draggedDocuments[0], stackDoc, (doc) => {
+                //         Cast(stackDoc.data, listSpec(Doc))!.push(doc);
+                //         return true;
+                //     });
+                // }
             }
         }
     }
@@ -426,6 +433,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems });
     }
     render() {
+        let self = this;
         let style = this.props.isOverlay ? "scroll" : "hidden";
         let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
         let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
@@ -435,7 +443,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     height: this.props.height ? this.props.height : undefined,
                     background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined,
                     opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
-                    color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial",
+                    color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
                     pointerEvents: interactive ? "all" : "none",
                     fontSize: "13px"
                 }}
-- 
cgit v1.2.3-70-g09d2


From 7fdb33cafaa3e8593d648ddba994356a9625ff56 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 9 Jul 2019 13:52:35 -0400
Subject: string list to list of text doc map in scraper and cleaned up kv pane
 templating interaction code

---
 src/client/views/nodes/DocumentContentsView.tsx |  7 +--
 src/client/views/nodes/KeyValueBox.tsx          | 20 ++++++--
 src/scraping/buxton/scraper.py                  | 61 +++++++++++++++++++++++--
 3 files changed, 75 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index b4c0e844f..eb786d537 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -69,7 +69,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             // this document as the data document for the layout.
             return this.props.Document;
         }
-        return this.props.DataDoc
+        return this.props.DataDoc;
     }
     get layoutDoc() {
         // if this document's layout field contains a document (ie, a rendering template), then we will use that
@@ -124,13 +124,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
-<<<<<<< HEAD
             components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
-            bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
-=======
-            components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
->>>>>>> b49fdb1c42b9758e006521e0f404634ba396a2ac
             jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index e27ab1589..e8619584e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -161,7 +161,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     getTemplate = async () => {
         let parent = Docs.StackingDocument([], { width: 800, height: 800, title: "Template" });
         parent.singleColumn = false;
-        parent.columnWidth = 50;
+        parent.columnWidth = 100;
         for (let row of this.rows.filter(row => row.isChecked)) {
             await this.createTemplateField(parent, row);
             row.uncheck();
@@ -175,7 +175,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         if (!sourceDoc) {
             return;
         }
-        let fieldTemplate = this.inferType(sourceDoc[metaKey], metaKey);
+        let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
 
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
@@ -200,12 +200,24 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
     }
 
-    inferType = (data: FieldResult, metaKey: string) => {
+    inferType = async (data: FieldResult, metaKey: string) => {
         let options = { width: 300, height: 300, title: metaKey };
         if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
             return Docs.TextDocument(options);
         } else if (data instanceof List) {
-            return Docs.StackingDocument([], options);
+            if (data.length === 0) {
+                return Docs.StackingDocument([], options);
+            }
+            let first = await Cast(data[0], Doc);
+            if (!first) {
+                return Docs.StackingDocument([], options);
+            }
+            switch (first.type) {
+                case "image":
+                    return Docs.StackingDocument([], options);
+                case "text":
+                    return Docs.TreeDocument([], options);
+            }
         } else if (data instanceof ImageField) {
             return Docs.ImageDocument("https://www.freepik.com/free-icon/picture-frame-with-mountain-image_748687.htm", options);
         }
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 02c6d8b74..700269727 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -26,7 +26,7 @@ def extract_links(fileName):
         item = rels[rel]
         if item.reltype == RT.HYPERLINK and ".aspx" not in item._target:
             links.append(item._target)
-    return listify(links)
+    return text_doc_map(links)
 
 
 def extract_value(kv_string):
@@ -60,6 +60,12 @@ def protofy(fieldId):
     }
 
 
+def text_doc_map(string_list):
+    def guid_map(caption):
+        return write_text_doc(caption)
+    return listify(proxify_guids(list(map(guid_map, string_list))))
+
+
 def write_schema(parse_results, display_fields, storage_key):
     view_guids = parse_results["child_guids"]
 
@@ -110,6 +116,54 @@ def write_schema(parse_results, display_fields, storage_key):
     return view_doc_guid
 
 
+def write_text_doc(content):
+    data_doc_guid = guid()
+    view_doc_guid = guid()
+
+    view_doc = {
+        "_id": view_doc_guid,
+        "fields": {
+            "proto": protofy(data_doc_guid),
+            "x": 10,
+            "y": 10,
+            "width": 400,
+            "zIndex": 2,
+            "libraryBrush": False
+        },
+        "__type": "Doc"
+    }
+
+    data_doc = {
+        "_id": data_doc_guid,
+        "fields": {
+            "proto": protofy("textProto"),
+            "data": {
+                "Data": '{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + content + '"}]}]},"selection":{"type":"text","anchor":1,"head":1}' + '}',
+                "__type": "RichTextField"
+            },
+            "title": content,
+            "nativeWidth": 200,
+            "author": "Bill Buxton",
+            "creationDate": {
+                "date": datetime.datetime.utcnow().microsecond,
+                "__type": "date"
+            },
+            "isPrototype": True,
+            "autoHeight": True,
+            "page": -1,
+            "nativeHeight": 200,
+            "height": 200,
+            "data_text": content
+        },
+        "__type": "Doc"
+    }
+
+    db.newDocuments.insert_one(view_doc)
+    db.newDocuments.insert_one(data_doc)
+
+    return view_doc_guid
+
+
 def write_image(folder, name):
     path = f"http://localhost:1050/files/{folder}/{name}"
 
@@ -253,7 +307,7 @@ def parse_document(file_name: str):
     while lines[cur] != "Image":
         link_descriptions.append(lines[cur].strip())
         cur += 1
-    result["link_descriptions"] = listify(link_descriptions)
+    result["link_descriptions"] = text_doc_map(link_descriptions)
 
     result["hyperlinks"] = extract_links(source + "/" + file_name)
 
@@ -265,7 +319,8 @@ def parse_document(file_name: str):
         captions.append(lines[cur + 1])
         cur += 2
     result["images"] = listify(images)
-    result["captions"] = listify(captions)
+
+    result["captions"] = text_doc_map(captions)
 
     notes = []
     if (cur < len(lines) and lines[cur] == "NOTES:"):
-- 
cgit v1.2.3-70-g09d2


From bcbfc7435a3b2234d8ac5244c4209d82f1732da0 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 10 Jul 2019 12:54:55 -0400
Subject: switched title/caption templates to be part of DocumentViews

---
 .../views/collections/CollectionSchemaView.tsx     |  2 ++
 .../views/collections/CollectionStackingView.tsx   |  7 +++++
 src/client/views/nodes/DocumentContentsView.tsx    | 29 +------------------
 src/client/views/nodes/DocumentView.tsx            | 33 ++++++++++++++++++++--
 4 files changed, 41 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b54e8aff0..148711c97 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -405,6 +405,7 @@ interface CollectionSchemaPreviewProps {
     renderDepth: number;
     width: () => number;
     height: () => number;
+    showOverlays?: (doc: Doc) => { title?: boolean, caption?: boolean };
     CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
     getTransform: () => Transform;
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -488,6 +489,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         fitToBox={this.props.fitToBox}
                         renderDepth={this.props.renderDepth + 1}
                         selectOnLoad={false}
+                        showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
                         moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index aea74321e..e6a3e8b68 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -12,6 +12,7 @@ import "./CollectionStackingView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { undoBatch } from "../../util/UndoManager";
 import { DragManager } from "../../util/DragManager";
+import { DocTypes } from "../../documents/Documents";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -61,6 +62,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this.createDropTarget(ele!);
     }
 
+    overlays = (doc: Doc) => {
+        return doc.type === DocTypes.IMG ? { title: true, caption: true } : {}
+    }
+
     @computed
     get singleColumnChildren() {
         return this.filteredChildren.map((d, i) => {
@@ -75,6 +80,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 <CollectionSchemaPreview
                     Document={layoutDoc}
                     DataDocument={d !== this.props.DataDoc ? this.props.DataDoc : undefined}
+                    showOverlays={this.overlays}
                     renderDepth={this.props.renderDepth}
                     width={width}
                     height={height}
@@ -122,6 +128,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     addDocument={this.props.addDocument}
                     moveDocument={this.props.moveDocument}
                     removeDocument={this.props.removeDocument}
+                    showOverlays={this.overlays}
                     getTransform={dxf}
                     width={width}
                     height={height}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index eb786d537..ed6b224a7 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -89,34 +89,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return new List<string>();
     }
     @computed get finalLayout() {
-        const baseLayout = this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;
-        let base = baseLayout;
-        let layout = baseLayout;
-
-        // bcz: templates are intended only for a document's primary layout or overlay (not background).  However, 
-        // a DocumentContentsView is used to render  annotation overlays, so we detect that here 
-        // by checking the layoutKey.  This should probably be moved into
-        // a prop so that the overlay can explicitly turn off templates.
-        if ((this.props.layoutKey === "overlayLayout" && StrCast(this.props.Document.layout).indexOf("CollectionView") !== -1) ||
-            (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1) ||
-            (this.props.layoutKey === "layout" && NumCast(this.props.Document.viewType)) !== CollectionViewType.Freeform) {
-            this.templates.forEach(template => {
-                let self = this;
-                // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
-                // than the width/height of the containing document
-                function convertConstantsToNative(match: string, offset: number, x: string) {
-                    let px = Number(match.replace("px", ""));
-                    return `${Math.min(NumCast(self.props.Document.height, 0),
-                        Math.min(NumCast(self.props.Document.width, 0),
-                            px * self.props.ScreenToLocalTransform().Scale))}px`;
-                }
-                // let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
-                // layout = nativizedTemplate.replace("{layout}", base);
-                layout = template.replace("{layout}", base);
-                base = layout;
-            });
-        }
-        return layout;
+        return this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;
     }
 
     render() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 430409ee3..5385238f0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -8,7 +8,7 @@ import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
 import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, Utils } from "../../../Utils";
+import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
@@ -34,6 +34,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { list, object, createSimpleSchema } from 'serializr';
 import { LinkManager } from '../../util/LinkManager';
 import { RouteStore } from '../../../server/RouteStore';
+import { FormattedTextBox } from './FormattedTextBox';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -78,6 +79,7 @@ export interface DocumentViewProps {
     moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     renderDepth: number;
+    showOverlays?: (doc: Doc) => { title?: boolean, caption?: boolean };
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
@@ -593,6 +595,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let foregroundColor = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.color : this.props.Document.color);
         var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
+        let showTitle = this.props.showOverlays && this.props.showOverlays(this.props.Document).title;
+        let showCaption = this.props.showOverlays && this.props.showOverlays(this.props.Document).caption;
+        let templates = Cast(this.props.Document.templates, listSpec("string"));
+        if (templates instanceof List) {
+            templates.map(str => {
+                if (str.indexOf("{props.Document.title}") !== -1) showTitle = true;
+                if (str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = true;
+            });
+        }
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -614,7 +625,25 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
             >
-                {this.contents}
+                {!showTitle && !showCaption ? this.contents :
+                    <div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
+                        {!showTitle ? (null) :
+                            <div style={{
+                                position: "absolute", top: 0, textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
+                                overflow: "hidden", width: `${100 * this.props.ContentScaling()}%`, height: 25, background: "rgba(0, 0, 0, .4)", color: "white",
+                                transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})`
+                            }}>
+                                <span>{this.props.Document.title}</span>
+                            </div>
+                        }
+                        {!showCaption ? (null) :
+                            <div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+                                <FormattedTextBox {...this.props} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} selectOnLoad={this.props.selectOnLoad} fieldExt={""} hideOnLeave={true} fieldKey={"caption"} />
+                            </div>
+                        }
+                        {this.contents}
+                    </div>
+                }
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

