From b4c2d2add2b863060ce559e49b3953f24050f162 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 14:47:44 -0500
Subject: can add strokes to mobile ink

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

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 73dc7edc6..d8b575092 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,7 +53,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
+            console.log("create drop", ele);
             if (ele) {
+                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
@@ -135,7 +137,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
-
         }
 
         @undoBatch
-- 
cgit v1.2.3-70-g09d2


From 3cca58612cde96a3082ca8e190fe2166d531d556 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 17:40:50 -0500
Subject: drawn strokes get added to mobile ink collection

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +--
 src/mobile/MobileInterface.scss                    |  7 +++
 src/mobile/MobileInterface.tsx                     | 62 ++++++++++++++--------
 .../authentication/models/current_user_utils.ts    |  2 +-
 6 files changed, 53 insertions(+), 30 deletions(-)
 create mode 100644 src/mobile/MobileInterface.scss

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 632f2f0d6..4d487c032 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -231,9 +231,9 @@ export default class GestureOverlay extends Touchable {
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+                {this.currentStroke}
                 {this.props.children}
                 {this._palette}
-                {this.currentStroke}
             </div>);
     }
 }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d8b575092..e94f24f2c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,9 +53,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
-            console.log("create drop", ele);
             if (ele) {
-                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c3e131184..8d376fb57 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -114,6 +114,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     private addDocument = (newBox: Doc) => {
         const added = this.props.addDocument(newBox);
+        console.log("adding doc from freeform", added);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -356,12 +357,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
-        console.log("on gesture");
         switch (ge.gesture) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
+                console.log("make stroke", inkDoc);
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -403,14 +404,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // I think it makes sense for the marquee menu to go away when panned. -syip2
-        MarqueeOptionsMenu.Instance.fadeOut(true);
+        MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
 
         let x = this.Document.panX || 0;
         let y = this.Document.panY || 0;
         const docs = this.childLayoutPairs.map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay) {
-            PDFMenu.Instance.fadeOut(true);
+            PDFMenu.Instance && PDFMenu.Instance.fadeOut(true);
             const minx = docs.length ? NumCast(docs[0].x) : 0;
             const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
             const miny = docs.length ? NumCast(docs[0].y) : 0;
@@ -990,6 +991,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
+        console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight());
         return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
new file mode 100644
index 000000000..e4cc919a5
--- /dev/null
+++ b/src/mobile/MobileInterface.scss
@@ -0,0 +1,7 @@
+.mobileInterface-topButtons {
+    position: absolute;
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    z-index: 9999;
+}
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 8d342d749..3cb08afa7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,10 +10,16 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
 import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
 import GestureOverlay from '../client/views/GestureOverlay';
+import { InkingControl } from '../client/views/InkingControl';
+import { InkTool } from '../new_fields/InkField';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import "./MobileInterface.scss";
+
+library.add(faTrash, faCheck);
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -22,6 +28,9 @@ export default class MobileInterface extends React.Component {
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
     @observable private currentView: "main" | "ink" | "library" = "main";
 
+    private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+    private inkDoc?: Doc;
+
     constructor(props: Readonly<{}>) {
         super(props);
         MobileInterface.Instance = this;
@@ -32,8 +41,8 @@ export default class MobileInterface extends React.Component {
         library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
 
         if (this.userDoc && !this.mainContainer) {
-            const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-            this.userDoc.activeMobile = doc;
+            // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+            this.userDoc.activeMobile = this.mainDoc;
         }
     }
 
@@ -44,13 +53,14 @@ export default class MobileInterface extends React.Component {
         if (this.userDoc) {
             switch (view) {
                 case "main": {
-                    const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.mainDoc;
                     break;
                 }
                 case "ink": {
-                    const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.inkDoc;
+                    InkingControl.Instance.switchTool(InkTool.Pen);
                     break;
                 }
             }
@@ -64,7 +74,7 @@ export default class MobileInterface extends React.Component {
                 Document={this.mainContainer}
                 DataDoc={undefined}
                 LibraryPath={emptyPath}
-                addDocument={undefined}
+                addDocument={returnFalse}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
@@ -89,40 +99,46 @@ export default class MobileInterface extends React.Component {
         return "hello";
     }
 
+    onClick = (e: React.MouseEvent) => {
+        // insert ink as collection into active displayed doc view
+        // reset ink collection
+        this.inkDoc = undefined;
+        console.log("clicked");
+        this.switchCurrentView("main");
+        InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+    }
+
     @computed
     get inkContent() {
-        // return <div>INK</div>;
+        // TODO: get props from active collection and pass to the new freeform
         if (this.mainContainer) {
             return (
                 <GestureOverlay>
-                    <CollectionFreeFormView
+                    <div className="mobileInterface-topButtons">
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Cancel drawing"><FontAwesomeIcon icon="trash" /></button>
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Insert drawing"><FontAwesomeIcon icon="check" /></button>
+                    </div>
+                    <CollectionView
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
                         fieldKey={""}
-                        addDocument={returnFalse}
-                        removeDocument={returnFalse}
-                        moveDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        PanelHeight={() => window.screen.height}
-                        PanelWidth={() => window.screen.width}
-                        annotationsKey={""}
-                        isAnnotationOverlay={false}
+                        PanelHeight={() => window.innerHeight}
+                        PanelWidth={() => window.innerWidth}
                         focus={emptyFunction}
-                        isSelected={returnTrue} //
+                        isSelected={returnTrue}
                         select={emptyFunction}
-                        active={returnTrue} //
+                        active={returnTrue}
                         ContentScaling={returnOne}
                         whenActiveChanged={returnFalse}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={Transform.Identity}
                         ruleProvider={undefined}
                         renderDepth={0}
                         ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        chromeCollapsed={true}>
-                    </CollectionFreeFormView>
+                        ContainingCollectionDoc={undefined}>
+                    </CollectionView>
                 </GestureOverlay>
             );
         }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b40179fbc..98092ddfe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -141,7 +141,7 @@ export class CurrentUserUtils {
     }
 
     static setupMobileInkingDoc(userDoc: Doc) {
-        return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" });
+        return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" });
     }
 
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
-- 
cgit v1.2.3-70-g09d2


From e8fcbbf57b2a2f443d9c280ce10558cf9d51c632 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Sun, 9 Feb 2020 17:11:24 -0500
Subject: can upload collection from mobile to desktop

---
 src/client/DocServer.ts                            |   9 +-
 src/client/util/DragManager.ts                     |   2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   1 +
 .../views/collections/CollectionStackingView.tsx   |   2 +
 .../CollectionStackingViewFieldColumn.tsx          |   1 +
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../views/collections/CollectionViewChromes.tsx    |   1 +
 src/mobile/ImageUpload.tsx                         |   1 +
 src/mobile/MobileInkOverlay.tsx                    |  53 +++++++-
 src/mobile/MobileInterface.scss                    |   8 ++
 src/mobile/MobileInterface.tsx                     | 150 ++++++++++++---------
 src/server/ApiManagers/UploadManager.ts            |   1 +
 src/server/Message.ts                              |   6 +
 src/server/Websocket/Websocket.ts                  |   7 +-
 .../authentication/models/current_user_utils.ts    |  21 +--
 src/server/server_Initialization.ts                |  24 ++--
 17 files changed, 195 insertions(+), 96 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index c03764471..e09251855 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,5 +1,5 @@
 import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
 import { Opt, Doc } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
@@ -81,6 +81,10 @@ export namespace DocServer {
             Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
         }
 
+        export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) {
+            Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content);
+        }
+
     }
 
     export function init(protocol: string, hostname: string, port: number, identifier: string) {
@@ -116,6 +120,9 @@ export namespace DocServer {
         _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => {
             MobileInkOverlay.Instance.updatePosition(content);
         });
+        _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => {
+            MobileInkOverlay.Instance.uploadDocument(content);
+        });
     }
 
     function errorFunc(): never {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index df2f5fe3c..0bb8b531d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -179,7 +179,7 @@ export namespace DragManager {
             );
         }
         element.dataset.canDrop = "true";
-        const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+        const handler = (e: Event) => { console.log("drop target reveied docs"); dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
         element.addEventListener("dashOnDrop", handler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 80752303c..26e0cc35a 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -73,6 +73,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
 
     @undoBatch
     rowDrop = action((e: Event, de: DragManager.DropEvent) => {
+        console.log("masronry row drop");
         this._createAliasSelected = false;
         if (de.complete.docDragData) {
             (this.props.parent.Document.dropConverter instanceof ScriptField) &&
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 992820fc7..83c90810e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -240,6 +240,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
+        console.log("DROP STACKIN G2");
         const where = [de.x, de.y];
         let targInd = -1;
         let plusOne = 0;
@@ -270,6 +271,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     onDrop = async (e: React.DragEvent): Promise<void> => {
+        console.log("DROP STACKING");
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 39b4e4e1d..65c4b3195 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -57,6 +57,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @undoBatch
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
+        console.log("column drop stacking");
         this._createAliasSelected = false;
         if (de.complete.docDragData) {
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e94f24f2c..b35af2314 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 88023783b..1d399e26f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -138,7 +138,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
-        ContextMenu.Instance.clearItems();
+        ContextMenu.Instance && ContextMenu.Instance.clearItems();
         if (index !== -1) {
             value.splice(index, 1);
             return true;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index a870b6043..01dc21f95 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -231,6 +231,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     @action
     toggleCollapse = () => {
+        console.log("toggle collapse");
         this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled";
         if (this.props.collapse) {
             this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled");
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 3304e8e22..10bd78075 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -47,6 +47,7 @@ class Uploader extends React.Component {
 
                     const upload = window.location.origin + "/upload";
                     this.status = "uploading image";
+                    console.log("uploading image", formData);
                     const res = await fetch(upload, {
                         method: 'POST',
                         body: formData
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 6b65aa436..ed4cca5b9 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,9 +1,13 @@
 import React = require('react');
 import { observer } from "mobx-react";
-import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent } from "../server/Message";
+import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
 import { observable, action } from "mobx";
 import { GestureUtils } from "../pen-gestures/GestureUtils";
 import "./MobileInkOverlay.scss";
+import { StrCast } from '../new_fields/Types';
+import { DragManager } from "../client/util/DragManager";
+import { DocServer } from '../client/DocServer';
+import { Doc } from '../new_fields/Doc';
 
 
 @observer
@@ -67,7 +71,7 @@ export default class MobileInkOverlay extends React.Component {
             height: bounds.height * this._scale,
         };
 
-        const target = document.elementFromPoint(points[0].X, points[0].Y);
+        const target = document.elementFromPoint(this._x + 10, this._y + 10);
         target?.dispatchEvent(
             new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
                 {
@@ -82,6 +86,43 @@ export default class MobileInkOverlay extends React.Component {
         );
     }
 
+    uploadDocument = async (content: MobileDocumentUploadContent) => {
+        const { docId } = content;
+        console.log("receive upload document id", docId);
+        const doc = await DocServer.GetRefField(docId);
+
+        if (doc && doc instanceof Doc) {
+            console.log("parsed upload document into doc", StrCast(doc.proto!.title));
+
+            const target = document.elementFromPoint(this._x + 10, this._y + 10);
+            console.log("the target is", target);
+
+            const dragData = new DragManager.DocumentDragData([doc]);
+            const complete = new DragManager.DragCompleteEvent(false, dragData);
+            console.log("the drag data is", dragData);
+
+            if (target) {
+                target.dispatchEvent(
+                    new CustomEvent<DragManager.DropEvent>("dashOnDrop",
+                        {
+                            bubbles: true,
+                            detail: {
+                                x: this._x,
+                                y: this._y,
+                                complete: complete,
+                                altKey: false,
+                                metaKey: false,
+                                ctrlKey: false
+                            }
+                        }
+                    )
+                );
+            } else {
+                alert("TARGET IS UNDEFINED");
+            }
+        }
+    }
+
     @action
     dragStart = (e: React.PointerEvent) => {
         document.removeEventListener("pointermove", this.dragging);
@@ -132,15 +173,17 @@ export default class MobileInkOverlay extends React.Component {
                     transform: `translate(${this._x}px, ${this._y}px)`,
                     zIndex: 30000,
                     pointerEvents: "none",
-                    borderStyle: this._isDragging ? "solid" : "dashed"
-                }}
+                    borderStyle: this._isDragging ? "solid" : "dashed",
+                    backgroundColor: "rgba(255, 0, 0, 0.3)"
+                }
+                }
                 ref={this._mainCont}
             >
                 <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
                 <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
-            </div>
+            </div >
         );
     }
 }
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
index 8abe5a40d..8b0ebcd53 100644
--- a/src/mobile/MobileInterface.scss
+++ b/src/mobile/MobileInterface.scss
@@ -1,7 +1,15 @@
 .mobileInterface-inkInterfaceButtons {
     position: absolute;
+    top: -50px;
     display: flex;
     justify-content: space-between;
     width: 100%;
     z-index: 9999;
+    height: 50px;
+}
+
+.mobileInterface-container {
+    height: calc(100% - 50px);
+    margin-top: 50px;
+    position: relative;
 }
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 665d9a168..a1ef0a5d1 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -22,6 +22,18 @@ import { SelectionManager } from '../client/util/SelectionManager';
 import { DateField } from '../new_fields/DateField';
 import { GestureUtils } from '../pen-gestures/GestureUtils';
 import { DocServer } from '../client/DocServer';
+import { DocumentDecorations } from '../client/views/DocumentDecorations';
+import { OverlayView } from '../client/views/OverlayView';
+import { DictationOverlay } from '../client/views/DictationOverlay';
+import SharingManager from '../client/util/SharingManager';
+import { PreviewCursor } from '../client/views/PreviewCursor';
+import { ContextMenu } from '../client/views/ContextMenu';
+import { RadialMenu } from '../client/views/nodes/RadialMenu';
+import PDFMenu from '../client/views/pdf/PDFMenu';
+import MarqueeOptionsMenu from '../client/views/collections/collectionFreeForm/MarqueeOptionsMenu';
+import GoogleAuthenticationManager from '../client/apis/GoogleAuthenticationManager';
+import { listSpec } from '../new_fields/Schema';
+import { Id } from '../new_fields/FieldSymbols';
 
 library.add(faLongArrowAltLeft);
 
@@ -31,7 +43,7 @@ export default class MobileInterface extends React.Component {
     @computed private get userDoc() { return CurrentUserUtils.UserDocument; }
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
     // @observable private currentView: "main" | "ink" | "upload" = "main";
-    private mainDoc: Doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+    private mainDoc: any = CurrentUserUtils.setupMobileDoc(this.userDoc);
     @observable private renderView?: () => JSX.Element;
 
     // private inkDoc?: Doc;
@@ -49,7 +61,6 @@ export default class MobileInterface extends React.Component {
         library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
 
         if (this.userDoc && !this.mainContainer) {
-            // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
             this.userDoc.activeMobile = this.mainDoc;
         }
     }
@@ -76,48 +87,22 @@ export default class MobileInterface extends React.Component {
         });
     }
 
-    // @action
-    // switchCurrentView = (view: "main" | "ink" | "upload") => {
-    //     this.currentView = view;
-
-    //     if (this.userDoc) {
-    //         switch (view) {
-    //             case "main": {
-    //                 // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-    //                 this.userDoc.activeMobile = this.mainDoc;
-    //                 break;
-    //             }
-    //             case "ink": {
-    //                 this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
-    //                 this.userDoc.activeMobile = this.inkDoc;
-    //                 InkingControl.Instance.switchTool(InkTool.Pen);
-    //                 this.drawingInk = true;
-
-    //                 DocServer.Mobile.dispatchOverlayTrigger({
-    //                     enableOverlay: true,
-    //                     width: window.innerWidth,
-    //                     height: window.innerHeight
-    //                 });
-
-    //                 break;
-    //             }
-    //             case "upload": {
-    //                 this.uploadDoc = CurrentUserUtils.setupMobileUploadDoc(this.userDoc);
-    //                 this.userDoc.activeMobile = this.uploadDoc;
-
-    //             }
-    //         }
-    //     }
-    // }
+    onSwitchUpload = () => {
+        DocServer.Mobile.dispatchOverlayTrigger({
+            enableOverlay: true,
+            width: 100,
+            height: 100
+        });
+    }
 
     renderDefaultContent = () => {
-        console.log("rendering default content");
+        console.log("rendering default content", this.mainContainer);
         if (this.mainContainer) {
             return <DocumentView
                 Document={this.mainContainer}
                 DataDoc={undefined}
                 LibraryPath={emptyPath}
-                addDocument={returnFalse}
+                addDocument={(doc: Doc) => { console.log("want to add doc to default content", StrCast(doc.title)); return false; }}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
@@ -192,42 +177,54 @@ export default class MobileInterface extends React.Component {
                             <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
                         </div>
                     </div>
-                    <GestureOverlay>
-                        <CollectionView
-                            Document={this.mainContainer}
-                            DataDoc={undefined}
-                            LibraryPath={emptyPath}
-                            fieldKey={""}
-                            addDocTab={returnFalse}
-                            pinToPres={emptyFunction}
-                            PanelHeight={() => window.innerHeight}
-                            PanelWidth={() => window.innerWidth}
-                            focus={emptyFunction}
-                            isSelected={returnFalse}
-                            select={emptyFunction}
-                            active={returnFalse}
-                            ContentScaling={returnOne}
-                            whenActiveChanged={returnFalse}
-                            ScreenToLocalTransform={Transform.Identity}
-                            ruleProvider={undefined}
-                            renderDepth={0}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}>
-                        </CollectionView>
-                    </GestureOverlay>
+                    <CollectionView
+                        Document={this.mainContainer}
+                        DataDoc={undefined}
+                        LibraryPath={emptyPath}
+                        fieldKey={""}
+                        addDocTab={returnFalse}
+                        pinToPres={emptyFunction}
+                        PanelHeight={() => window.innerHeight}
+                        PanelWidth={() => window.innerWidth}
+                        focus={emptyFunction}
+                        isSelected={returnFalse}
+                        select={emptyFunction}
+                        active={returnFalse}
+                        ContentScaling={returnOne}
+                        whenActiveChanged={returnFalse}
+                        ScreenToLocalTransform={Transform.Identity}
+                        ruleProvider={undefined}
+                        renderDepth={0}
+                        ContainingCollectionView={undefined}
+                        ContainingCollectionDoc={undefined}>
+                    </CollectionView>
                 </div>
             );
         }
     }
 
-    upload = () => {
+    upload = async (e: React.MouseEvent) => {
+        if (this.mainContainer) {
+            const data = Cast(this.mainContainer.data, listSpec(Doc));
+            if (data) {
+                const uploadDoc = await data[1]; // TODO: ensure this is the collection to upload
 
+                console.log("UPLOADING DOCUMENT FROM MOBILE", uploadDoc[Id], StrCast(uploadDoc.proto!.title));
+                if (uploadDoc) {
+                    DocServer.Mobile.dispatchMobileDocumentUpload({
+                        docId: uploadDoc[Id]
+                    });
+                }
+            }
+        }
+        e.stopPropagation();
+        e.preventDefault();
     }
 
     renderUploadContent() {
         if (this.mainContainer) {
             return (
-                <div className="mobileInterface">
+                <div className="mobileInterface" onDragOver={this.onDragOver}>
                     <div className="mobileInterface-inkInterfaceButtons">
                         <div className="navButtons">
                             <button className="mobileInterface-button cancel" onClick={this.onBack} title="Back">BACK</button>
@@ -240,7 +237,7 @@ export default class MobileInterface extends React.Component {
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
-                        addDocument={returnFalse}
+                        addDocument={(doc: Doc) => { console.log("want to add doc", StrCast(doc.title)); return false; }}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
                         removeDocument={undefined}
@@ -266,13 +263,35 @@ export default class MobileInterface extends React.Component {
         }
     }
 
+    onDragOver = (e: React.DragEvent) => {
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
     render() {
         // const content = this.currentView === "main" ? this.mainContent :
         //     this.currentView === "ink" ? this.inkContent :
         //         this.currentView === "upload" ? this.uploadContent : <></>;
         return (
-            <div className="mobile-container">
-                {this.renderView ? this.renderView() : this.renderDefaultContent()}
+            <div className="mobileInterface-container" onDragOver={this.onDragOver}>
+                {/* <DocumentDecorations />
+                <GestureOverlay>
+                    {this.renderView ? this.renderView() : this.renderDefaultContent()}
+                </GestureOverlay> */}
+
+                {/* <DictationOverlay />
+                <SharingManager />
+                <GoogleAuthenticationManager /> */}
+                <DocumentDecorations />
+                <GestureOverlay>
+                    {this.renderView ? this.renderView() : this.renderDefaultContent()}
+                </GestureOverlay>
+                <PreviewCursor />
+                {/* <ContextMenu /> */}
+                <RadialMenu />
+                {/* <PDFMenu />
+                <MarqueeOptionsMenu />
+                <OverlayView /> */}
             </div>
         );
     }
@@ -281,5 +300,6 @@ export default class MobileInterface extends React.Component {
 Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); });
 Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); });
 Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); });
+Scripting.addGlobal(function onSwitchMobileUpload() { return MobileInterface.Instance.onSwitchUpload(); });
 Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); });
 
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 74f45ae62..e76d9b7a2 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -42,6 +42,7 @@ export default class UploadManager extends ApiManager {
             method: Method.POST,
             subscription: "/upload",
             secureHandler: async ({ req, res }) => {
+                console.log("/upload register");
                 const form = new formidable.IncomingForm();
                 form.uploadDir = pathToDirectory(Directory.parsed_files);
                 form.keepExtensions = true;
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 236df3f3c..c23a2f0a8 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,5 +1,6 @@
 import { Utils } from "../Utils";
 import { Point } from "../pen-gestures/ndollar";
+import { Doc } from "../new_fields/Doc";
 
 export class Message<T> {
     private _name: string;
@@ -62,6 +63,10 @@ export interface UpdateMobileInkOverlayPositionContent {
     readonly dsize?: number;
 }
 
+export interface MobileDocumentUploadContent {
+    readonly docId: string;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -75,6 +80,7 @@ export namespace MessageStore {
     export const GesturePoints = new Message<GestureContent>("Gesture Points");
     export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
     export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>("Update Mobile Ink Overlay Position");
+    export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>("Upload Document From Mobile");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 77816c897..798bdae67 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
 import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../Message";
 import { Client } from "../Client";
 import { Socket } from "socket.io";
 import { Database } from "../database";
@@ -57,6 +57,7 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
             Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
             Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
+            Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 
@@ -83,6 +84,10 @@ export namespace WebSocket {
         socket.broadcast.emit("receiveUpdateOverlayPosition", content);
     }
 
+    function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
+        socket.broadcast.emit("receiveMobileDocumentUpload", content);
+    }
+
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
         const { ProjectCredentials } = GoogleCredentialsLoader;
         switch (query.type) {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 817cf40b1..3e5953ac1 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -103,7 +103,7 @@ export class CurrentUserUtils {
             { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
             { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc },
-            { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload);', backgroundColor: "orange" },
+            { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" },
             { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" },
         ];
         return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
@@ -138,7 +138,9 @@ export class CurrentUserUtils {
     }
 
     static setupMobileDoc(userDoc: Doc) {
-        return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
+        const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" });
+
+        return userDoc.activeMoble ?? Docs.Create.MasonryDocument([webDoc, ...CurrentUserUtils.setupMobileButtons(userDoc)], {
             columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
         });
     }
@@ -148,13 +150,14 @@ export class CurrentUserUtils {
     }
 
     static setupMobileUploadDoc(userDoc: Doc) {
-        console.log("setup mobile upload", window.innerWidth, window.innerHeight);
-        const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" });
-        const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload", backgroundColor: "pink" });
-        return Docs.Create.StackingDocument([webDoc, uploadDoc], {
-            title: "Mobile Upload", backgroundColor: "white",
-            columnWidth: window.innerWidth, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", autoHeight: true, yMargin: 5,
-            width: window.innerWidth, height: window.innerHeight
+        const webDoc = Docs.Create.WebDocument("https://yahoo.com", { title: "Upload Images From the Web", chromeStatus: "enabled" });
+        const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload Collection", backgroundColor: "pink" });
+        console.log("window size", window.innerWidth, window.innerHeight);
+        // return Docs.Create.StackingDocument([webDoc, uploadDoc], {
+        //     columnWidth: window.innerWidth, //ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "Mobile Upload", autoHeight: true, yMargin: 5
+        // });
+        return Docs.Create.StackingDocument([webDoc, uploadDoc], {//...CurrentUserUtils.setupMobileButtons(userDoc)], {
+            columnWidth: 100, lockedPosition: true, chromeStatus: "disabled", title: "Upload", autoHeight: true, yMargin: 30
         });
     }
 
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index cbe070293..5f1ecc733 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -42,18 +42,18 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
         }
     };
     app.use(cors(corsOptions));
-    app.use("*", ({ user, originalUrl }, res, next) => {
-        if (user && !originalUrl.includes("Heartbeat")) {
-            const userEmail = (user as any).email;
-            if (userEmail) {
-                timeMap[userEmail] = Date.now();
-            }
-        }
-        if (!user && originalUrl === "/") {
-            return res.redirect("/login");
-        }
-        next();
-    });
+    // app.use("*", ({ user, originalUrl }, res, next) => {
+    //     if (user && !originalUrl.includes("Heartbeat")) {
+    //         const userEmail = (user as any).email;
+    //         if (userEmail) {
+    //             timeMap[userEmail] = Date.now();
+    //         }
+    //     }
+    //     if (!user && originalUrl === "/") {
+    //         return res.redirect("/login");
+    //     }
+    //     next();
+    // });
 
     app.use(wdm(compiler, { publicPath: config.output.publicPath }));
     app.use(whm(compiler));
-- 
cgit v1.2.3-70-g09d2


From dba26063754e6a882c8a3eb238b04a935989f406 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 10 Feb 2020 04:52:04 -0500
Subject: started long press for iframe to copy image

---
 src/client/util/DragManager.ts                     |   6 +-
 src/client/views/collections/CollectionSubView.tsx |   2 +
 src/client/views/nodes/WebBox.tsx                  | 114 ++++++++++++++++++++-
 3 files changed, 119 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0bb8b531d..745540ff9 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -179,7 +179,7 @@ export namespace DragManager {
             );
         }
         element.dataset.canDrop = "true";
-        const handler = (e: Event) => { console.log("drop target reveied docs"); dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
+        const handler = (e: Event) => { dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
         element.addEventListener("dashOnDrop", handler);
         return () => {
             element.removeEventListener("dashOnDrop", handler);
@@ -265,6 +265,10 @@ export namespace DragManager {
         StartDrag([ele], dragData, downX, downY, options);
     }
 
+    export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) {
+        StartDrag([ele], {}, downX, downY);
+    }
+
     function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
         eles = eles.filter(e => e);
         if (!dragDiv) {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b35af2314..b5067ea9e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -180,6 +180,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
             const html = e.dataTransfer.getData("text/html");
             const text = e.dataTransfer.getData("text/plain");
+            console.log(html);
 
             if (text && text.startsWith("<div")) {
                 return;
@@ -212,6 +213,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
                 if (img) {
                     const split = img.split("src=\"")[1].split("\"")[0];
+                    console.log("creating image", split);
                     const doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
                     ImageUtils.ExtractExif(doc);
                     this.props.addDocument(doc);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 0b23c3bec..c405dd9d4 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -21,6 +21,9 @@ import "./WebBox.scss";
 import React = require("react");
 import { DocAnnotatableComponent } from "../DocComponent";
 import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { DragManager } from "../../util/DragManager";
+import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 
 library.add(faStickyNote);
 
@@ -34,6 +37,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
+    private _longPressSecondsHack?: NodeJS.Timeout;
+    private _iframeRef = React.createRef<HTMLIFrameElement>();
+    private _iframeDragRef = React.createRef<HTMLDivElement>();
+    @observable private _pressX: number = 0;
+    @observable private _pressY: number = 0;
+
     componentWillMount() {
 
         const field = Cast(this.props.Document[this.props.fieldKey], WebField);
@@ -51,6 +60,49 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         this.setURL();
     }
 
+    componentDidMount() {
+        document.addEventListener("pointerup", this.onLongPressUp);
+        document.addEventListener("pointermove", this.onLongPressMove);
+        // this._iframeRef.current?.contentWindow?.document.addEventListener("mousedown", (event: MouseEvent) => {
+        //     console.log("clicked inside the iframe?");
+        // });
+        // const iframe = document.getElementById(this.props.Document.proto![Id]);
+        // if (iframe) {
+        //     iframe.addEventListener('pointerdown', function (event) {
+        //         const B = iframe.getBoundingClientRect();
+        //         const e = new CustomEvent('pointerdown', { bubbles: true, cancelable: false });
+        //         // e.clientX = e.clientX + B?.left;
+        //         // e.clientY = e.clientY + B?.top;
+        //         console.log("custom event pointer down");
+        //         iframe.dispatchEvent(e);
+        //     })
+        // }
+        // if (this._iframeRef.current) {
+        //     console.log("resetting iframes events");
+        //     const self = this;
+        //     this._iframeRef.current.addEventListener('pointermove', function (event) {
+        //         const B = self._iframeRef.current?.getBoundingClientRect();
+        //         const e = new CustomEvent('pointermove', { bubbles: true, cancelable: false });
+        //         // e.clientX = e.clientX + B?.left;
+        //         // e.clientY = e.clientY + B?.top;
+        //         self._iframeRef.current?.dispatchEvent(e);
+        //     });
+        //     this._iframeRef.current.addEventListener('pointerdown', function (event) {
+        //         const B = self._iframeRef.current?.getBoundingClientRect();
+        //         const e = new CustomEvent('pointerdown', { bubbles: true, cancelable: false });
+        //         // e.clientX = e.clientX + B?.left;
+        //         // e.clientY = e.clientY + B?.top;
+        //         console.log("custom event pointer down");
+        //         self._iframeRef.current?.dispatchEvent(e);
+        //     })
+        // }
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerup", this.onLongPressUp);
+        document.removeEventListener("pointermove", this.onLongPressMove);
+    }
+
     @action
     onURLChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.url = e.target.value;
@@ -165,6 +217,62 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         }
     }
 
+    // TODO: make this actually a long press
+    onLongPressDown = (e: React.PointerEvent) => {
+
+        console.log("press down", e.clientX, e.clientX);
+        this._pressX = e.clientX;
+        this._pressY = e.clientY;
+        this._longPressSecondsHack = setTimeout(() => {
+            console.log("start the drag!!");
+            const B = this._iframeRef.current?.getBoundingClientRect();
+            const iframeDoc = this._iframeRef.current?.contentDocument;
+            if (B && iframeDoc) {
+                console.log("frame doc", iframeDoc);
+                console.log("get point", this._pressX, B.left, this._pressY, B.top);
+                const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top);
+                console.log("found element", element);
+                if (element) {
+                    e.stopPropagation();
+                    e.preventDefault();
+                    const clone = element.cloneNode(true) as HTMLElement;
+
+                    if (clone.nodeName === "IMG") {
+                        const src = clone.getAttribute("src"); // TODO: may not always work
+
+                        if (src) {
+                            const doc = Docs.Create.ImageDocument(src, { width: 300 });
+                            ImageUtils.ExtractExif(doc);
+
+                            console.log("start image drag", this._pressX, this._pressY, doc);
+                            // document.dispatchEvent()
+                            const dragData = new DragManager.DocumentDragData([doc]);
+                            DragManager.StartDocumentDrag([clone], dragData, this._pressX, this._pressY);
+                        }
+                    }
+                }
+            }
+        }, 1500);
+        // e.stopPropagation();
+        // e.preventDefault();
+    }
+
+    onLongPressMove = (e: PointerEvent) => {
+        this._pressX = e.clientX;
+        this._pressY = e.clientY;
+    }
+
+    onLongPressUp = (e: PointerEvent) => {
+        console.log("press up");
+        if (this._longPressSecondsHack) {
+            clearTimeout(this._longPressSecondsHack);
+            console.log("long press cancelled");
+        }
+        // e.stopPropagation();
+        // e.preventDefault();
+    }
+
+
     @computed
     get content() {
         const field = this.dataDoc[this.props.fieldKey];
@@ -172,9 +280,9 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
         if (field instanceof HtmlField) {
             view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
         } else if (field instanceof WebField) {
-            view = <iframe src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+            view = <iframe ref={this._iframeRef} src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
         } else {
-            view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+            view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
         }
         const content =
             <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
@@ -192,7 +300,9 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 <div className={classname}  >
                     {content}
                 </div>
+                <div className="mobileIframeDragger" ref={this._iframeDragRef} draggable></div>
                 {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
+                {/* onPointerDown={(e) => { this.onPrePointer(e); this.onLongPressDown(e) }} onPointerMove={this.onPrePointer} onPointerUp={(e) => { this.onPrePointer(e); }} />} */}
             </>);
     }
     render() {
-- 
cgit v1.2.3-70-g09d2


From 2131c829b8632a87b8ba52bc311d3b0346eed3f7 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 11 Feb 2020 21:36:20 -0500
Subject: did the maths :')

---
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 74 +++++++++++++++-------
 3 files changed, 53 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8679c8bd1..12906ee83 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -51,11 +51,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         private _childLayoutDisposer?: IReactionDisposer;
+        protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
+                this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index b70697e9a..2213b7882 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -101,7 +101,7 @@
 }
 
 .pullpane-indicator {
-    z-index: 999;
+    z-index: 99999;
     background-color: rgba($color: #000000, $alpha: .4);
     position: absolute;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8f555f315..bf517e7c2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload, faTextHeight } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
@@ -47,6 +47,7 @@ import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { MainView } from "../../MainView";
+import { TouchScrollableMenuItem } from "../../TouchScrollableMenu";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -627,12 +628,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         // use the centerx and centery as the "new mouse position"
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                        // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
 
                         if (!this._pullDirection) { // if we are not bezel movement
                             this.pan({ clientX: centerX, clientY: centerY });
                         } else {
                             this._pullCoords = [centerX, centerY];
-                            console.log(MainView.Instance.flyoutWidth);
                         }
 
                         this._lastX = centerX;
@@ -657,22 +658,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (pt1 && pt2) {
                 const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                // const screenPoint = this.getTransform().inverse().transformPoint(centerX, centerY);
                 this._lastX = centerX;
                 this._lastY = centerY;
+                const screenBox = this._mainCont?.getBoundingClientRect();
 
+
+                // console.log(this.props.PanelWidth(), transformed[0]);
                 // determine if we are using a bezel movement
-                if ((this.props.PanelWidth() - this._lastX) < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "right";
-                } else if (this._lastX < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "left";
-                } else if (this.props.PanelHeight() - this._lastY < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "bottom";
-                } else if (this._lastY < 120) { // to account for header
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "top";
+                if (screenBox) {
+                    if ((screenBox.right - centerX) < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "right";
+                    } else if (centerX - screenBox.left < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "left";
+                    } else if (screenBox.bottom - centerY < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "bottom";
+                    } else if (centerY - screenBox.top < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "top";
+                    }
                 }
 
 
@@ -708,6 +715,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             default:
                 break;
         }
+        console.log("");
 
         this._pullDirection = "";
         this._pullCoords = [0, 0];
@@ -1120,6 +1128,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     render() {
         TraceMobx();
+        const clientRect = this._mainCont?.getBoundingClientRect();
+        // console.log('clientrect has upd8ed', clientRect, this._pullCoords);
+        console.log(
+            'left', clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - MainView.Instance.flyoutWidth : clientRect.x - MainView.Instance.flyoutWidth : "auto",
+            'top indicator', clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : clientRect.y - 20 : "auto",
+            'width', clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+            'height', clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0);
+        console.log(clientRect);
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         // this.Document.fitX = this.contentBounds && this.contentBounds.x;
         // this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1146,16 +1162,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
-                    // width: this._pullDirection === "left" || this._pullDirection === "right" ? Math.abs(this.props.PanelWidth() - this._pullCoords[0]) : this.props.PanelWidth(),
-                    // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
-                    // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
-                    // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
-                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? MainView.Instance.getPWidth() - this._pullCoords[0] + MainView.Instance.flyoutWidth : MainView.Instance.getPWidth(),
-                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? MainView.Instance.getPHeight() - this._pullCoords[1] : MainView.Instance.getPHeight(),
-                    left: this._pullDirection === "right" ? undefined : 0,
-                    right: this._pullDirection === "right" ? 0 : undefined,
-                    top: this._pullDirection === "bottom" ? undefined : 0,
-                    bottom: this._pullDirection === "bottom" ? 0 : undefined
+                    // width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    // height: clientRect ? this._pullDirection === "top" ? this._pullCoords[0] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[0] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "right" ? undefined : clientRect.left : 0,
+                    // right: clientRect ? this._pullDirection === "left" ? undefined : clientRect.right : 0,
+                    // top: clientRect ? this._pullDirection === "bottom" ? undefined : clientRect.top : 0,
+                    // bottom: clientRect ? this._pullDirection === "top" ? undefined : clientRect.bottom : 0,
+
+
+
+                    // hahahahahahahhahahahaa
+
+                    width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" || this._pullDirection === "bottom" ? clientRect.left - MainView.Instance.flyoutWidth : this._pullCoords[0] - clientRect.left : undefined,
+                    // top: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" ? clientRect.top - 20 : this._pullCoords[1] - clientRect.top : undefined,
+
+                    // left: x-axis, top: y-axis
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+
                 }}>
             </div>
 
-- 
cgit v1.2.3-70-g09d2


From dbdf81e191107240a62086ebe65d1dc5e3b503c6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 3 Mar 2020 00:18:36 -0500
Subject: several fixes to templates (simplified expanding, notes use 'text'
 field now, collections show documents when their data field is not a list). 
 multicol/row resizers select their doc.

---
 package-lock.json                                  | 95 +++++++++-------------
 src/client/documents/Documents.ts                  | 23 ++++--
 src/client/util/DropConverter.ts                   | 12 ++-
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  7 +-
 .../CollectionMulticolumnView.tsx                  |  9 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |  2 +
 src/client/views/nodes/DocumentContentsView.tsx    | 16 +---
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/new_fields/Doc.ts                              |  9 +-
 .../authentication/models/current_user_utils.ts    | 15 ++--
 11 files changed, 88 insertions(+), 106 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 827fb05b8..ef3ecc9f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -832,7 +832,7 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
                 "@types/express": "*"
@@ -2226,7 +2226,7 @@
                 },
                 "util": {
                     "version": "0.10.3",
-                    "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
+                    "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
                     "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
                     "dev": true,
                     "requires": {
@@ -2846,7 +2846,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2880,7 +2880,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -3051,7 +3051,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3844,7 +3844,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3856,7 +3856,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4398,7 +4398,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -5697,8 +5697,7 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5716,13 +5715,11 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5735,18 +5732,15 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5849,8 +5843,7 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5860,7 +5853,6 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5873,20 +5865,17 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "minipass": {
                     "version": "2.9.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5903,7 +5892,6 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5984,8 +5972,7 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5995,7 +5982,6 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -6071,8 +6057,7 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -6102,7 +6087,6 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -6120,7 +6104,6 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -6159,13 +6142,11 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "yallist": {
                     "version": "3.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 }
             }
         },
@@ -7383,7 +7364,7 @@
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7438,7 +7419,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -8169,7 +8150,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8506,7 +8487,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8538,7 +8519,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8713,7 +8694,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -9051,7 +9032,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -9134,7 +9115,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12785,7 +12766,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12798,7 +12779,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -13038,7 +13019,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -14464,7 +14445,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14906,7 +14887,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -15186,7 +15167,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -16048,7 +16029,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -16078,7 +16059,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -16094,7 +16075,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16911,7 +16892,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -18383,7 +18364,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index abef72f21..4df90ceb8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -94,6 +94,7 @@ export interface DocumentOptions {
     layoutKey?: string;
     type?: string;
     title?: string;
+    style?: string;
     page?: number;
     scale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
@@ -183,7 +184,7 @@ export namespace Docs {
 
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
-                layout: { view: FormattedTextBox, dataField: data },
+                layout: { view: FormattedTextBox, dataField: "text" },
                 options: { _height: 150, _xMargin: 10, _yMargin: 10 }
             }],
             [DocumentType.HIST, {
@@ -442,7 +443,7 @@ export namespace Docs {
          * only when creating a DockDocument from the current user's already existing
          * main document.
          */
-        export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string) {
+        export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") {
             const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
 
             if (!("author" in protoProps)) {
@@ -455,7 +456,7 @@ export namespace Docs {
 
             protoProps.isPrototype = true;
 
-            const dataDoc = MakeDataDelegate(proto, protoProps, data);
+            const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
             const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
 
             AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title));
@@ -473,10 +474,10 @@ export namespace Docs {
          * @param options initial values to apply to this new delegate
          * @param value the data to store in this new delegate
          */
-        function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D) {
+        function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data") {
             const deleg = Doc.MakeDelegate(proto);
             if (value !== undefined) {
-                deleg.data = value;
+                deleg[fieldKey] = value;
             }
             return Doc.assign(deleg, options);
         }
@@ -535,7 +536,7 @@ export namespace Docs {
         }
 
         export function TextDocument(text: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options);
+            return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options, undefined, "text");
         }
 
         export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
@@ -929,7 +930,15 @@ export namespace DocUtils {
             description: "Add Note ...",
             subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({
                 description: ":" + StrCast(note.title),
-                event: (args: { x: number, y: number }) => docTextAdder(Docs.Create.TextDocument("", { _width: 200, x, y, _autoHeight: note._autoHeight !== false, layout: note, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) })),
+                event: (args: { x: number, y: number }) => {
+                    const textDoc = Docs.Create.TextDocument("", {
+                        _width: 200, x, y, _autoHeight: note._autoHeight !== false,
+                        title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1)
+                    });
+                    textDoc.layoutKey = "layout_" + note.title;
+                    textDoc[textDoc.layoutKey] = note;
+                    docTextAdder(textDoc);
+                },
                 icon: "eye"
             })) as ContextMenuProps[],
             icon: "eye"
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 3c7caa60b..393e39687 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,5 +1,5 @@
 import { DragManager } from "./DragManager";
-import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../new_fields/Doc";
 import { DocumentType } from "../documents/DocumentTypes";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { StrCast } from "../../new_fields/Types";
@@ -8,7 +8,12 @@ import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { RichTextField } from "../../new_fields/RichTextField";
 import { ImageField } from "../../new_fields/URLField";
 
-export function makeTemplate(doc: Doc, first: boolean = true): boolean {
+// 
+// converts 'doc' into a template that can be used to render other documents.
+// the title of doc is used to determine which field is being templated, so
+// passing a value for 'rename' allows the doc to be given a meangingful name 
+// after it has been converted to
+export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean {
     const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
     const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0];
     const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, "");
@@ -29,6 +34,7 @@ export function makeTemplate(doc: Doc, first: boolean = true): boolean {
             any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc));
         }
     }
+    rename && (doc.title = rename);
     return any;
 }
 export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
@@ -38,7 +44,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
         if (!doc.onDragStart && !doc.onClick && !doc.isButtonBar) {
             const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
             if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) {
-                makeTemplate(layoutDoc);
+                !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
             } else {
                 (layoutDoc.layout instanceof Doc) && !data.userDropAction;
             }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index aa31d604e..527623ad4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -113,7 +113,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             return Cast(this.dataField, listSpec(Doc));
         }
         get childDocs() {
-            const docs = DocListCast(this.dataField);
+            const dfield = this.dataField;
+            const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate && !this.props.annotationsKey ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
+            const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
         }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d4f1a5444..af701347f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -109,12 +109,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
             this.props.addLiveTextDocument(
                 Docs.Create.TextDocument("", { _width: NumCast((FormattedTextBox.DefaultLayout as Doc)?._width) || 200, _height: 100, layout: FormattedTextBox.DefaultLayout, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
-        } else if (e.keyCode > 48 && e.keyCode <= 57) {
-            const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
-            const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
-            text.layout = notes[(e.keyCode - 49) % notes.length];
-            this.props.addLiveTextDocument(text);
-        }
+        } 
         e.stopPropagation();
     }
     //heuristically converts pasted text into a table.
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 82175c0b5..bd20781dc 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -223,17 +223,13 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
      */
     @computed
     private get contents(): JSX.Element[] | null {
-        // bcz: feels like a hack ... trying to show something useful when there's no list document in the data field of a templated object
-        const expanded = Cast(this.props.Document.expandedTemplate, Doc, null);
-        let { childLayoutPairs } = this.dataDoc[this.props.fieldKey] instanceof List || !expanded ? this : { childLayoutPairs: [] } as { childLayoutPairs: { layout: Doc, data: Doc }[] };
-        const replaced = !childLayoutPairs.length && !Cast(expanded?.layout, Doc, null) && expanded;
-        childLayoutPairs = childLayoutPairs.length || !replaced ? childLayoutPairs : [{ layout: replaced, data: replaced }];
+        let { childLayoutPairs } = this;
         const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
             const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin));
-            const width = () => expanded ? this.props.PanelWidth() : this.lookupPixels(layout);
+            const width = () => this.lookupPixels(layout);
             const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             collector.push(
                 <div className={"document-wrapper"}
@@ -248,6 +244,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
                 <ResizeBar
                     width={resizerWidth}
                     key={"resizer" + i}
+                    select={this.props.select}
                     columnUnitLength={this.getColumnUnitLength}
                     toLeft={layout}
                     toRight={childLayoutPairs[i + 1]?.layout}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 2cbeb3526..e1e604686 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -11,6 +11,7 @@ interface ResizerProps {
     columnUnitLength(): number | undefined;
     toLeft?: Doc;
     toRight?: Doc;
+    select: (isCtrlPressed: boolean) => void;
 }
 
 const resizerOpacity = 1;
@@ -23,6 +24,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
 
     @action
     private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+        this.props.select(false);
         e.stopPropagation();
         e.preventDefault();
         window.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 41478a3c5..dcb6d4a31 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -73,21 +73,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     get dataDoc() {
-        if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") {
-            // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), 
-            // then we render the layout document as a template and use this document as the data context for the template layout.
-            const proto = Doc.GetProto(this.props.Document);
-            return proto instanceof Promise ? undefined : proto;
-        }
-        return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc;
+        const proto = this.props.DataDoc || Doc.GetProto(this.props.Document);
+        return proto instanceof Promise ? undefined : proto;
     }
     get layoutDoc() {
-        if (this.props.LayoutDoc || (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string")) {
-            // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), 
-            // then we render the layout document as a template and use this document as the data context for the template layout.
-            return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document);
-        }
-        return Doc.Layout(this.props.Document);
+        return Doc.expandTemplateLayout(this.props.LayoutDoc?.() || Doc.Layout(this.props.Document), this.props.Document);
     }
 
     CreateBindings(): JsxBindings {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 41c94b923..7b434ebc1 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -207,7 +207,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
 
     updateTitle = () => {
-        if ((this.props.Document.isTemplateForField === "data" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
             const str = this._editorView.state.doc.textContent;
             const titlestr = str.substr(0, Math.min(40, str.length));
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index dcd97f079..0f3896055 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -449,12 +449,11 @@ export namespace Doc {
     }
 
     //
-    // Determines whether the combination of the layoutDoc and dataDoc represents
-    // a template relationship :  there is a dataDoc and it doesn't match the layoutDoc an
-    // the lyouatDoc's layout is layout string (not a document) 
+    // Determines whether the layout needs to be expanded (as a template).
+    // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template
     //
     export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
-        return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc && !(Doc.LayoutField(layoutDoc) instanceof Doc);
+        return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc;
     }
 
     //
@@ -610,7 +609,7 @@ export namespace Doc {
     export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
 
         // find the metadata field key that this template field doc will display (indicated by its title)
-        const metadataFieldKey = StrCast(templateField.title).replace(/^-/, "");
+        const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, "");
 
         // update the original template to mark it as a template
         templateField.isTemplateForField = metadataFieldKey;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index dc63f8a89..6216ab7e6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -43,15 +43,15 @@ export class CurrentUserUtils {
             Docs.Create.TextDocument("completed", { title: "completed", _backgroundColor: "green", color: "white" })
         ];
         const noteTemplates = [
-            Docs.Create.TextDocument("", { title: "Note", isTemplateDoc: true, backgroundColor: "yellow" }),
-            Docs.Create.TextDocument("", { title: "Idea", isTemplateDoc: true, backgroundColor: "pink" }),
-            Docs.Create.TextDocument("", { title: "Topic", isTemplateDoc: true, backgroundColor: "lightBlue" }),
-            Docs.Create.TextDocument("", { title: "Person", isTemplateDoc: true, backgroundColor: "lightGreen" }),
-            Docs.Create.TextDocument("", { title: "Todo", isTemplateDoc: true, backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption" })
+            Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Idea", isTemplateDoc: true, backgroundColor: "pink" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Topic", isTemplateDoc: true, backgroundColor: "lightBlue" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Person", isTemplateDoc: true, backgroundColor: "lightGreen" }),
+            Docs.Create.TextDocument("", { title: "text", style: "Todo", isTemplateDoc: true, backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption" })
         ];
         doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" });
         Doc.enumeratedTextTemplate(Doc.GetProto(noteTemplates[4]), FormattedTextBox.LayoutString("Todo"), "taskStatus", taskStatusValues);
-        doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt) ? nt : nt), { title: "Note Types", _height: 75 }));
+        doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt, true, StrCast(nt.style)) ? nt : nt), { title: "Note Types", _height: 75 }));
     }
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
@@ -285,7 +285,8 @@ export class CurrentUserUtils {
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.descriptionBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: descriptionTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "sticky-note" });
-        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc, doc.descriptionBtn as Doc], {
+        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc, doc.descriptionBtn as Doc,
+        ...DocListCast(Cast(doc.noteTypes, Doc, null))], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
             backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true,
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
-- 
cgit v1.2.3-70-g09d2

