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

---
 src/client/views/nodes/PDFBox.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 88cd2cdc4..1f3887608 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -65,9 +65,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
     public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); }
     public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); }
-    public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); }
+    public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
-    public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); }
+    public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
 
     @undoBatch
     @action
@@ -128,7 +128,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                         <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div>
                 </button>
-                <input value={`${NumCast(this.props.Document.curPage)}`}
+                <input value={`${(this.Document.curPage || 1)}`}
                     onChange={e => this.gotoPage(Number(e.currentTarget.value))}
                     style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
                     onClick={action(() => this._pageControls = !this._pageControls)} />
-- 
cgit v1.2.3-70-g09d2


From 77d66d159d75442ff5635c4bf4843b6155883cc2 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 10 Oct 2019 14:04:22 -0400
Subject: removed CollectionVideoView

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/DocumentManager.ts                 |   5 +-
 src/client/util/SharingManager.tsx                 |   3 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/PreviewCursor.tsx                 |  18 +--
 .../views/collections/CollectionBaseView.tsx       |  15 +-
 .../views/collections/CollectionSchemaCells.tsx    |   7 +-
 .../views/collections/CollectionSchemaView.tsx     |   9 +-
 src/client/views/collections/CollectionSubView.tsx |  17 +-
 .../views/collections/CollectionVideoView.scss     |  51 ------
 .../views/collections/CollectionVideoView.tsx      | 115 -------------
 .../CollectionFreeFormLinkView.tsx                 |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   8 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  14 +-
 src/client/views/linking/LinkFollowBox.tsx         |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 src/client/views/nodes/FieldView.tsx               |   5 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 src/client/views/nodes/VideoBox.scss               |  57 ++++++-
 src/client/views/nodes/VideoBox.tsx                | 177 +++++++++++++++++++--
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  18 ++-
 23 files changed, 284 insertions(+), 258 deletions(-)
 delete mode 100644 src/client/views/collections/CollectionVideoView.scss
 delete mode 100644 src/client/views/collections/CollectionVideoView.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9d1a6ed3e..22aa74634 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,6 @@
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
 import { HistogramOperation } from "../northstar/operations/HistogramOperation";
-import { CollectionVideoView } from "../views/collections/CollectionVideoView";
 import { CollectionView } from "../views/collections/CollectionView";
 import { CollectionViewType } from "../views/collections/CollectionBaseView";
 import { AudioBox } from "../views/nodes/AudioBox";
@@ -137,7 +136,7 @@ export namespace Docs {
                 options: { height: 150 }
             }],
             [DocumentType.VID, {
-                layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType },
+                layout: { view: VideoBox, ext: anno },
                 options: { currentTimecode: 0 },
             }],
             [DocumentType.AUDIO, {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index c95d923cb..24285a70a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -3,7 +3,6 @@ import { Doc, DocListCastAsync } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { Cast, NumCast, StrCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionVideoView } from '../views/collections/CollectionVideoView';
 import { CollectionView } from '../views/collections/CollectionView';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { LinkManager } from './LinkManager';
@@ -56,7 +55,7 @@ export class DocumentManager {
         return this.getDocumentViewsById(doc[Id]);
     }
 
-    public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionVideoView): DocumentView | undefined {
+    public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined {
 
         let toReturn: DocumentView | undefined;
         let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
@@ -81,7 +80,7 @@ export class DocumentManager {
         return toReturn;
     }
 
-    public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionVideoView): DocumentView | undefined {
+    public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined {
         return this.getDocumentViewById(toFind[Id], preferredCollection);
     }
 
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index c989b6c17..d37cd1b80 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -17,7 +17,6 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
 import { DocumentView } from "../views/nodes/DocumentView";
 import { SelectionManager } from "./SelectionManager";
 import { DocumentManager } from "./DocumentManager";
-import { CollectionVideoView } from "../views/collections/CollectionVideoView";
 import { CollectionView } from "../views/collections/CollectionView";
 
 library.add(fa.faCopy);
@@ -185,7 +184,7 @@ export default class SharingManager extends React.Component<{}> {
                 className={"focus-span"}
                 title={title}
                 onClick={() => {
-                    let context: Opt<CollectionVideoView | CollectionView>;
+                    let context: Opt<CollectionView>;
                     if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) {
                         DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
                     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4f9bdbe9c..1d9f0c74b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -333,7 +333,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         iconDoc.y = NumCast(doc.y) - 24;
         iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document));
         selected.length === 1 && (doc.minimizedDoc = iconDoc);
-        selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
+        selected[0].props.addDocument && selected[0].props.addDocument(iconDoc);
         return iconDoc;
     }
     @action
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 1aed51e64..eed2cc5da 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -14,7 +14,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _onKeyPress?: (e: KeyboardEvent) => void;
     static _getTransform: () => Transform;
     static _addLiveTextDoc: (doc: Doc) => void;
-    static _addDocument: (doc: Doc, allowDuplicates: false) => boolean;
+    static _addDocument: (doc: Doc) => boolean;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     //when focus is lost, this will remove the preview cursor
@@ -44,7 +44,7 @@ export class PreviewCursor extends React.Component<{}> {
                             title: url, width: 400, height: 315,
                             nativeWidth: 600, nativeHeight: 472.5,
                             x: newPoint[0], y: newPoint[1]
-                        }), false);
+                        }));
                         return;
                     }
 
@@ -56,7 +56,7 @@ export class PreviewCursor extends React.Component<{}> {
                             title: url, width: 300, height: 300,
                             // nativeWidth: 300, nativeHeight: 472.5,
                             x: newPoint[0], y: newPoint[1]
-                        }), false);
+                        }));
                         return;
                     }
 
@@ -79,11 +79,11 @@ export class PreviewCursor extends React.Component<{}> {
 
                     let img: Doc = Docs.Create.ImageDocument(
                         arr[1], {
-                            width: 300, title: arr[1],
-                            x: newPoint[0],
-                            y: newPoint[1],
-                        });
-                    PreviewCursor._addDocument(img, false);
+                        width: 300, title: arr[1],
+                        x: newPoint[0],
+                        y: newPoint[1],
+                    });
+                    PreviewCursor._addDocument(img);
                     return;
                 }
 
@@ -113,7 +113,7 @@ export class PreviewCursor extends React.Component<{}> {
         onKeyPress: (e: KeyboardEvent) => void,
         addLiveText: (doc: Doc) => void,
         getTransform: () => Transform,
-        addDocument: (doc: Doc, allowDuplicates: false) => boolean) {
+        addDocument: (doc: Doc) => boolean) {
         this._clickPoint = [x, y];
         this._onKeyPress = onKeyPress;
         this._addLiveTextDoc = addLiveText;
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 62be1fc31..61919427a 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -45,7 +45,7 @@ export namespace CollectionViewType {
 }
 
 export interface CollectionRenderProps {
-    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument: (document: Doc) => boolean;
     removeDocument: (document: Doc) => boolean;
     moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     active: () => boolean;
@@ -100,22 +100,13 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
 
     @action.bound
-    addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
-        var curTime = NumCast(this.props.Document.currentTimecode, -1);
-        curTime !== -1 && (doc.displayTimecode = curTime);
+    addDocument(doc: Doc): boolean {
         if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
         let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
         let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
-        const value = Cast(targetDataDoc[targetField], listSpec(Doc));
-        if (value !== undefined) {
-            if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
-                value.push(doc);
-            }
-        } else {
-            Doc.GetProto(targetDataDoc)[targetField] = new List([doc]);
-        }
+        Doc.AddDocToList(targetDataDoc, targetField, doc);
         Doc.GetProto(doc).lastOpened = new DateField;
         return true;
     }
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index fd1362848..79c032723 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -14,15 +14,12 @@ import '../DocumentDecorations.scss';
 import { EditableView } from "../EditableView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
-import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../new_fields/Types";
 import { Docs } from "../../documents/Documents";
-import { DocumentContentsView } from "../nodes/DocumentContentsView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faExpand } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { KeyCodes } from "../../northstar/utils/KeyCodes";
 import { undoBatch } from "../../util/UndoManager";
@@ -33,8 +30,8 @@ export interface CellProps {
     row: number;
     col: number;
     rowProps: CellInfo;
-    CollectionView: Opt<CollectionView | CollectionVideoView>;
-    ContainingCollection: Opt<CollectionView | CollectionVideoView>;
+    CollectionView: Opt<CollectionView>;
+    ContainingCollection: Opt<CollectionView>;
     Document: Doc;
     fieldKey: string;
     renderDepth: number;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 670c6bd43..3218f630a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -23,7 +23,6 @@ import '../DocumentDecorations.scss';
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
-import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionSchemaHeader, CollectionSchemaAddColumnHeader } from "./CollectionSchemaHeaders";
@@ -246,8 +245,8 @@ export interface SchemaTableProps {
     PanelHeight: () => number;
     PanelWidth: () => number;
     childDocs?: Doc[];
-    CollectionView: Opt<CollectionView | CollectionVideoView>;
-    ContainingCollectionView: Opt<CollectionView | CollectionVideoView>;
+    CollectionView: Opt<CollectionView>;
+    ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
     fieldKey: string;
     renderDepth: number;
@@ -904,11 +903,11 @@ interface CollectionSchemaPreviewProps {
     ruleProvider: Doc | undefined;
     focus?: (doc: Doc) => void;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
-    CollectionView?: CollectionView | CollectionVideoView;
+    CollectionView?: CollectionView;
     CollectionDoc?: Doc;
     onClick?: ScriptField;
     getTransform: () => Transform;
-    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument: (document: Doc) => boolean;
     moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
     removeDocument: (document: Doc) => boolean;
     active: () => boolean;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5e2b79278..689adc375 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -18,7 +18,6 @@ import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
 import { FieldViewProps } from "../nodes/FieldView";
 import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox";
-import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
 var path = require('path');
@@ -26,7 +25,7 @@ import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 
 export interface CollectionViewProps extends FieldViewProps {
-    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument: (document: Doc) => boolean;
     removeDocument: (document: Doc) => boolean;
     moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     PanelWidth: () => number;
@@ -37,7 +36,7 @@ export interface CollectionViewProps extends FieldViewProps {
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
-    CollectionView: Opt<CollectionView | CollectionVideoView>;
+    CollectionView: Opt<CollectionView>;
     ruleProvider: Doc | undefined;
 }
 
@@ -175,14 +174,14 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                         DocServer.GetRefField(docid).then(f => {
                             if (f instanceof Doc) {
                                 if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f, false);
+                                (f instanceof Doc) && this.props.addDocument(f);
                             }
                         });
                     } else {
                         this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, options));
                     }
                 } else if (text) {
-                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }), false);
+                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }));
                 }
                 return;
             }
@@ -194,7 +193,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     let split = img.split("src=\"")[1].split("\"")[0];
                     let doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
                     ImageUtils.ExtractExif(doc);
-                    this.props.addDocument(doc, false);
+                    this.props.addDocument(doc);
                     return;
                 } else {
                     let path = window.location.origin + "/doc/";
@@ -203,12 +202,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                         DocServer.GetRefField(docid).then(f => {
                             if (f instanceof Doc) {
                                 if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f, false);
+                                (f instanceof Doc) && this.props.addDocument(f);
                             }
                         });
                     } else {
                         let htmlDoc = Docs.Create.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
-                        this.props.addDocument(htmlDoc, false);
+                        this.props.addDocument(htmlDoc);
                     }
                     return;
                 }
@@ -252,7 +251,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             let type = result["content-type"];
                             if (type) {
                                 Docs.Get.DocumentFromType(type, str, { ...options, width: 300, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300 })
-                                    .then(doc => doc && this.props.addDocument(doc, false));
+                                    .then(doc => doc && this.props.addDocument(doc));
                             }
                         });
                     promises.push(prom);
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
deleted file mode 100644
index 509851ebb..000000000
--- a/src/client/views/collections/CollectionVideoView.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-
-.collectionVideoView-cont{
-    width: 100%;
-    height: 100%;
-    position: inherit;
-    top: 0;
-    left:0;
-    z-index: -1;
-    display:inline-table;
-}
-.collectionVideoView-time{
-    color : white;
-    top :25px;
-    left : 25px;
-    position: absolute;
-    background-color: rgba(50, 50, 50, 0.2);
-    transform-origin: left top;
-}
-.collectionVideoView-snapshot{
-    color : white;
-    top :25px;
-    right : 25px;
-    position: absolute;
-    background-color: rgba(50, 50, 50, 0.2);
-    transform-origin: left top;
-}
-.collectionVideoView-play {
-    width: 25px;
-    height: 20px;
-    bottom: 25px;
-    left : 25px;
-    position: absolute;
-    color : white;
-    background-color: rgba(50, 50, 50, 0.2);
-    border-radius: 4px;
-    text-align: center;
-    transform-origin: left bottom;
-}
-.collectionVideoView-full {
-    width: 25px;
-    height: 20px;
-    bottom: 25px;
-    right : 25px;
-    position: absolute;
-    color : white;
-    background-color: rgba(50, 50, 50, 0.2);
-    border-radius: 4px;
-    text-align: center;
-    transform-origin: right bottom;
-
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
deleted file mode 100644
index 3d898b7de..000000000
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { action } from "mobx";
-import { observer } from "mobx-react";
-import { NumCast } from "../../../new_fields/Types";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { VideoBox } from "../nodes/VideoBox";
-import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import "./CollectionVideoView.scss";
-import React = require("react");
-import { InkingControl } from "../InkingControl";
-import { InkTool } from "../../../new_fields/InkField";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-
-@observer
-export class CollectionVideoView extends React.Component<FieldViewProps> {
-    private _videoBox?: VideoBox;
-
-    public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") {
-        return FieldView.LayoutString(CollectionVideoView, fieldKey, fieldExt);
-    }
-    private get uIButtons() {
-        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
-        let curTime = NumCast(this.props.Document.currentTimecode);
-        return ([<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
-            <span>{"" + Math.round(curTime)}</span>
-            <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
-        </div>,
-        <div className="collectionVideoView-snapshot" key="time" onPointerDown={this.onSnapshot} style={{ transform: `scale(${scaling})` }}>
-            <FontAwesomeIcon icon="camera" size="lg" />
-        </div>,
-        VideoBox._showControls ? (null) : [
-            <div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
-                <FontAwesomeIcon icon={this._videoBox && this._videoBox.Playing ? "pause" : "play"} size="lg" />
-            </div>,
-            <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
-                F
-            </div>
-        ]]);
-    }
-
-    @action
-    onPlayDown = () => {
-        if (this._videoBox) {
-            if (this._videoBox.Playing) {
-                this._videoBox.Pause();
-            } else {
-                this._videoBox.Play();
-            }
-        }
-    }
-
-    @action
-    onFullDown = (e: React.PointerEvent) => {
-        if (this._videoBox) {
-            this._videoBox.FullScreen();
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-
-    @action
-    onSnapshot = (e: React.PointerEvent) => {
-        if (this._videoBox) {
-            this._videoBox.Snapshot();
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-
-    _isclick = 0;
-    @action
-    onResetDown = (e: React.PointerEvent) => {
-        if (this._videoBox) {
-            this._videoBox.Pause();
-            e.stopPropagation();
-            this._isclick = 0;
-            document.addEventListener("pointermove", this.onPointerMove, true);
-            document.addEventListener("pointerup", this.onPointerUp, true);
-            InkingControl.Instance.switchTool(InkTool.Eraser);
-        }
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent) => {
-        this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY);
-        if (this._videoBox) {
-            this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
-        }
-        e.stopImmediatePropagation();
-    }
-    @action
-    onPointerUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.onPointerMove, true);
-        document.removeEventListener("pointerup", this.onPointerUp, true);
-        InkingControl.Instance.switchTool(InkTool.None);
-        this._isclick < 10 && (this.props.Document.currentTimecode = 0);
-    }
-    setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
-
-    private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        let props = { ...this.props, ...renderProps };
-        return (<>
-            <CollectionFreeFormView {...props} setVideoBox={this.setVideoBox} CollectionView={this} chromeCollapsed={true} />
-            {this.props.isSelected() ? this.uIButtons : (null)}
-        </>);
-    }
-
-    render() {
-        return (
-            <CollectionBaseView {...this.props} className="collectionVideoView-cont" >
-                {this.subView}
-            </CollectionBaseView>);
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index df089eb00..fe92eed10 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -10,7 +10,7 @@ export interface CollectionFreeFormLinkViewProps {
     A: Doc;
     B: Doc;
     LinkDocs: Doc[];
-    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument: (document: Doc) => boolean;
     removeDocument: (document: Doc) => boolean;
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 38488f033..d2644480c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -93,10 +93,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
         !this.Document.isRuleProvider && (newBox.heading = heading);
-        this.addDocument(newBox, false);
+        this.addDocument(newBox);
     }
-    private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
-        let added = this.props.addDocument(newBox, false);
+    private addDocument = (newBox: Doc) => {
+        let added = this.props.addDocument(newBox);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -659,7 +659,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             if (doc instanceof Doc) {
                                 const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
                                 doc.x = xx, doc.y = yy;
-                                this.props.addDocument && this.props.addDocument(doc, false);
+                                this.props.addDocument && this.props.addDocument(doc);
                             }
                         }
                     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index eaf65b88c..bb787106c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -24,7 +24,7 @@ interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
     container: CollectionFreeFormView;
-    addDocument: (doc: Doc, allowDuplicates: false) => boolean;
+    addDocument: (doc: Doc) => boolean;
     activeDocuments: () => Doc[];
     selectDocuments: (docs: Doc[]) => void;
     removeDocument: (doc: Doc) => boolean;
@@ -83,7 +83,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 ns.map(line => {
                     let indent = line.search(/\S|$/);
                     let newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
-                    this.props.addDocument(newBox, false);
+                    this.props.addDocument(newBox);
                     y += 40 * this.props.getTransform().Scale;
                 });
             })();
@@ -92,7 +92,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             navigator.clipboard.readText().then(text => {
                 let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
                 if (ns.length === 1 && text.startsWith("http")) {
-                    this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
+                    this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
                 } else {
                     this.pasteTable(ns, x, y);
                 }
@@ -146,7 +146,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             }
             let newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
 
-            this.props.addDocument(newCol, false);
+            this.props.addDocument(newCol);
         }
     }
     @action
@@ -202,7 +202,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
     }
 
-    setPreviewCursor = (x: number, y: number, drag: boolean) => {
+    setPreviewCursor = action((x: number, y: number, drag: boolean) => {
         if (drag) {
             this._downX = this._lastX = x;
             this._downY = this._lastY = y;
@@ -217,7 +217,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this._downY = y;
             PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
         }
-    }
+    })
 
     @action
     onClick = (e: React.MouseEvent): void => {
@@ -350,7 +350,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
             }
             else {
-                this.props.addDocument(newCollection, false);
+                this.props.addDocument(newCollection);
                 this.props.selectDocuments([newCollection]);
             }
             this.cleanupInteractions(false);
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index b18aa5d63..32ebe7c61 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -278,7 +278,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                     alias.width = width;
                     alias.height = height;
 
-                    this.sourceView.props.addDocument(alias, false);
+                    this.sourceView.props.addDocument(alias);
                 }
             }
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a824ae9cc..e4b2ecffd 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -9,7 +9,6 @@ import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { LinkFollowBox } from "../linking/LinkFollowBox";
 import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
@@ -100,7 +99,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null);
         return <ObserverJsxParser
             blacklistedAttrs={[]}
-            components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox }}
+            components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox }}
             bindings={this.CreateBindings()}
             jsx={this.finalLayout}
             showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0bf3b5c40..758222e52 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -21,7 +21,6 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -65,13 +64,13 @@ library.add(fa.faLock);
 library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
 
 export interface DocumentViewProps {
-    ContainingCollectionView: Opt<CollectionView | CollectionVideoView>;
+    ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
     Document: Doc;
     DataDoc?: Doc;
     fitToBox?: boolean;
     onClick?: ScriptField;
-    addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
     moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
@@ -217,7 +216,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace");
             maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab"));
             if (maxLocation === "inPlace") {
-                expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc, false));
+                expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc));
                 let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
                 DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs);
             } else {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c17730f48..074efaf6c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -8,7 +8,6 @@ import { List } from "../../../new_fields/List";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField";
 import { Transform } from "../../util/Transform";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { AudioBox } from "./AudioBox";
 import { FormattedTextBox } from "./FormattedTextBox";
@@ -28,7 +27,7 @@ export interface FieldViewProps {
     fieldExt: string;
     leaveNativeSize?: boolean;
     fitToBox?: boolean;
-    ContainingCollectionView: Opt<CollectionView | CollectionVideoView>;
+    ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
     ruleProvider: Doc | undefined;
     Document: Doc;
@@ -37,7 +36,7 @@ export interface FieldViewProps {
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     renderDepth: number;
-    addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument?: (document: Doc) => boolean;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     removeDocument?: (document: Doc) => boolean;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1f3887608..57803be1f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -174,7 +174,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
         ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
     }
-    _initialScale: number | undefined;
+    _initialScale: number | undefined;  // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not.  Getting bigger after startup won't make it automatically be live....
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive");
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index d651a8621..b3cd439aa 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,6 +1,14 @@
+// .videoBox-container {
+//     .collectionfreeformview-container {
+//         mix-blend-mode: multiply;
+//     }
+// }
+
 .videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
 .videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen {
     width: 100%;
+    z-index: 0;  // logically this should be 0 (or unset) which would give us transparent brush strokes over videos.  However, this makes Chrome crawl to a halt
+    position: absolute;
 }
 
 .videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen {
@@ -11,7 +19,52 @@
     height: 100%;
 }
 
-.videoBox-content-interactive, .videoBox-content-fullScreen,
-.videoBox-content-YouTube-fullScreen  {
+.videoBox-content-interactive, .videoBox-content-fullScreen, .videoBox-content-YouTube-fullScreen  {
     pointer-events: all;
+}
+
+.videoBox-time{
+    color : white;
+    top :25px;
+    left : 25px;
+    position: absolute;
+    background-color: rgba(50, 50, 50, 0.2);
+    transform-origin: left top;
+    pointer-events:all;
+}
+.videoBox-snapshot{
+    color : white;
+    top :25px;
+    right : 25px;
+    position: absolute;
+    background-color:rgba(50, 50, 50, 0.2);
+    transform-origin: left top;
+    pointer-events:all;
+}
+.videoBox-play {
+    width: 25px;
+    height: 20px;
+    bottom: 25px;
+    left : 25px;
+    position: absolute;
+    color : white;
+    background-color: rgba(50, 50, 50, 0.2);
+    border-radius: 4px;
+    text-align: center;
+    transform-origin: left bottom;
+    pointer-events:all;
+}
+.videoBox-full {
+    width: 25px;
+    height: 20px;
+    bottom: 25px;
+    right : 25px;
+    position: absolute;
+    color : white;
+    background-color: rgba(50, 50, 50, 0.2);
+    border-radius: 4px;
+    text-align: center;
+    transform-origin: right bottom;
+    pointer-events:all;
+
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e83aa8bea..feb067d8f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,11 +3,11 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
 import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface, createSchema } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
+import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema";
+import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
 import { RouteStore } from "../../../server/RouteStore";
-import { Utils } from "../../../Utils";
+import { Utils, emptyFunction, returnOne } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
@@ -22,6 +22,8 @@ import { faVideo } from "@fortawesome/free-solid-svg-icons";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { positionSchema } from "./CollectionFreeFormDocumentView";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 var path = require('path');
 
 export const timeSchema = createSchema({
@@ -34,19 +36,24 @@ library.add(faVideo);
 
 @observer
 export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+    static _youtubeIframeCounter: number = 0;
     private _reactionDisposer?: IReactionDisposer;
     private _youtubeReactionDisposer?: IReactionDisposer;
     private _youtubePlayer: YT.Player | undefined = undefined;
     private _videoRef: HTMLVideoElement | null = null;
     private _youtubeIframeId: number = -1;
     private _youtubeContentCreated = false;
-    static _youtubeIframeCounter: number = 0;
+    private _downX: number = 0;
+    private _downY: number = 0;
+    private _isResetClick = 0;
+    private _isChildActive = false;
+    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     @observable _forceCreateYouTubeIFrame = false;
     @observable static _showControls: boolean;
     @observable _playTimer?: NodeJS.Timeout = undefined;
     @observable _fullScreen = false;
     @observable public Playing: boolean = false;
-    public static LayoutString() { return FieldView.LayoutString(VideoBox); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); }
 
     public get player(): HTMLVideoElement | null {
         return this._videoRef;
@@ -123,8 +130,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
             //convert to desired file format
             var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
-            let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString()));
-            VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => {
+            let filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_")));
+            VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
                 if (returnedFilename) {
                     let url = this.choosePath(Utils.prepend(returnedFilename));
                     let imageSummary = Docs.Create.ImageDocument(url, {
@@ -132,7 +139,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                         width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
                     });
                     imageSummary.isButton = true;
-                    this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
+                    this.props.addDocument && this.props.addDocument(imageSummary);
                     DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot");
                 }
             });
@@ -259,7 +266,73 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         });
 
     }
+    private get uIButtons() {
+        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+        let curTime = NumCast(this.props.Document.currentTimecode);
+        return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
+            <span>{"" + Math.round(curTime)}</span>
+            <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
+        </div>,
+        <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} style={{ transform: `scale(${scaling})` }}>
+            <FontAwesomeIcon icon="camera" size="lg" />
+        </div>,
+        VideoBox._showControls ? (null) : [
+            <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
+                <FontAwesomeIcon icon={this.Playing ? "pause" : "play"} size="lg" />
+            </div>,
+            <div className="collecvideoBoxtionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
+                F
+            </div>
+        ]]);
+    }
+
+    @action
+    onPlayDown = () => {
+        if (this.Playing) {
+            this.Pause();
+        } else {
+            this.Play();
+        }
+    }
 
+    @action
+    onFullDown = (e: React.PointerEvent) => {
+        this.FullScreen();
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+    @action
+    onSnapshot = (e: React.PointerEvent) => {
+        this.Snapshot();
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+    @action
+    onResetDown = (e: React.PointerEvent) => {
+        this.Pause();
+        e.stopPropagation();
+        this._isResetClick = 0;
+        document.addEventListener("pointermove", this.onResetMove, true);
+        document.addEventListener("pointerup", this.onResetUp, true);
+        InkingControl.Instance.switchTool(InkTool.Eraser);
+    }
+
+    @action
+    onResetMove = (e: PointerEvent) => {
+        this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
+        this.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
+        e.stopImmediatePropagation();
+    }
+    @action
+    onResetUp = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.onResetMove, true);
+        document.removeEventListener("pointerup", this.onResetUp, true);
+        InkingControl.Instance.switchTool(InkTool.None);
+        this._isResetClick < 10 && (this.props.Document.currentTimecode = 0);
+    }
+    @computed get fieldExtensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
     @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
 
     @computed get youtubeContent() {
@@ -273,11 +346,95 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         ></iframe>;
     }
 
+
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
+        this._setPreviewCursor = func;
+    }
+
+    @action.bound
+    removeDocument(doc: Doc): boolean {
+        Doc.GetProto(doc).annotationOn = undefined;
+        //TODO This won't create the field if it doesn't already exist
+        let targetDataDoc = this.fieldExtensionDoc;
+        let targetField = this.props.fieldExt;
+        let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
+        let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
+        index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
+        index !== -1 && value.splice(index, 1);
+        return true;
+    }
+    // this is called with the document that was dragged and the collection to move it into.
+    // if the target collection is the same as this collection, then the move will be allowed.
+    // otherwise, the document being moved must be able to be removed from its container before
+    // moving it into the target.  
+    @action.bound
+    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+            return true;
+        }
+        return this.removeDocument(doc) ? addDocument(doc) : false;
+    }
+
+    @action.bound
+    addDocument(doc: Doc): boolean {
+        Doc.GetProto(doc).annotationOn = this.props.Document;
+        var curTime = NumCast(this.props.Document.currentTimecode, -1);
+        curTime !== -1 && (doc.displayTimecode = curTime);
+        Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
+        return true;
+    }
+    whenActiveChanged = (isActive: boolean) => {
+        this._isChildActive = isActive;
+        this.props.whenActiveChanged(isActive);
+    }
+    active = () => {
+        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+    }
+    onClick = (e: React.MouseEvent) => {
+        this._setPreviewCursor &&
+            e.button === 0 &&
+            Math.abs(e.clientX - this._downX) < 3 &&
+            Math.abs(e.clientY - this._downY) < 3 &&
+            this._setPreviewCursor(e.clientX, e.clientY, false);
+    }
+
+    onPointerDown = (e: React.PointerEvent): void => {
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        if ((e.button !== 0 || e.altKey) && this.active()) {
+            this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
+        }
+    }
+    @computed get annotationLayer() {
+        return <CollectionFreeFormView {...this.props}
+            setPreviewCursor={this.setPreviewCursor}
+            PanelHeight={this.props.PanelHeight}
+            PanelWidth={this.props.PanelWidth}
+            focus={this.props.focus}
+            isSelected={this.props.isSelected}
+            select={emptyFunction}
+            active={this.active}
+            ContentScaling={returnOne}
+            whenActiveChanged={this.whenActiveChanged}
+            removeDocument={this.removeDocument}
+            moveDocument={this.moveDocument}
+            addDocument={this.addDocument}
+            CollectionView={undefined}
+            ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+            ruleProvider={undefined}
+            renderDepth={this.props.renderDepth + 1}
+            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+            chromeCollapsed={true}>
+        </CollectionFreeFormView>
+    }
+
     render() {
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
-        return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}>
+        return (<div className={"videoBox-container"} onClick={this.onClick} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu}>
             {this.youtubeVideoId ? this.youtubeContent : this.content}
-        </div>;
+            {this.annotationLayer}
+            {this.uIButtons}
+        </div >);
     }
 }
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 29eef27a0..bb4c5adc9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -91,7 +91,7 @@ export class WebBox extends React.Component<FieldViewProps> {
         });
 
         SelectionManager.SelectedDocuments().map(dv => {
-            dv.props.addDocument && dv.props.addDocument(newBox, false);
+            dv.props.addDocument && dv.props.addDocument(newBox);
             dv.props.removeDocument && dv.props.removeDocument(dv.props.Document);
         });
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 65ca830d9..366861144 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -18,7 +18,6 @@ import PDFMenu from "./PDFMenu";
 import "./PDFViewer.scss";
 import React = require("react");
 import * as rp from "request-promise";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import Annotation from "./Annotation";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -50,10 +49,10 @@ interface IViewerProps {
     GoToPage?: (n: number) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
-    addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
+    addDocument?: (doc: Doc) => boolean;
     setPdfViewer: (view: PDFViewer) => void;
     ScreenToLocalTransform: () => Transform;
-    ContainingCollectionView: Opt<CollectionView | CollectionVideoView>;
+    ContainingCollectionView: Opt<CollectionView>;
     whenActiveChanged: (isActive: boolean) => void;
 }
 
@@ -582,12 +581,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         return this.removeDocument(doc) ? addDocument(doc) : false;
     }
-
-
+    @action.bound
+    addDocument(doc: Doc): boolean {
+        Doc.GetProto(doc).annotationOn = this.props.Document;
+        Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc);
+        return true;
+    }
     @action.bound
     removeDocument(doc: Doc): boolean {
         Doc.GetProto(doc).annotationOn = undefined;
-        //TODO This won't create the field if it doesn't already exist
         let targetDataDoc = this.props.fieldExtensionDoc;
         let targetField = this.props.fieldExt;
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
@@ -659,8 +661,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     whenActiveChanged={this.whenActiveChanged}
                     removeDocument={this.removeDocument}
                     moveDocument={this.moveDocument}
-                    addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
-                    CollectionView={this.props.ContainingCollectionView}
+                    addDocument={this.addDocument}
+                    CollectionView={undefined}
                     ScreenToLocalTransform={this.scrollXf}
                     ruleProvider={undefined}
                     renderDepth={this.props.renderDepth + 1}
-- 
cgit v1.2.3-70-g09d2


From 21aab2fbf28e9cf6d08366b57d368d120d6813bb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 10 Oct 2019 16:42:12 -0400
Subject: got rid of all CollectionView wrappers around XxxBox's  added
 DocAnnotatableComponent

---
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/RichTextSchema.tsx                 |   4 +-
 src/client/views/DocComponent.tsx                  |  50 ++++++-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  20 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   8 +-
 src/client/views/nodes/ImageBox.scss               |   8 +-
 src/client/views/nodes/ImageBox.tsx                |  44 +++++--
 src/client/views/nodes/PDFBox.tsx                  |  24 ++--
 src/client/views/nodes/VideoBox.scss               |   8 +-
 src/client/views/nodes/VideoBox.tsx                | 143 ++++++---------------
 src/client/views/nodes/WebBox.tsx                  |  69 +++++++---
 src/client/views/pdf/Annotation.tsx                |   8 +-
 src/client/views/pdf/PDFViewer.tsx                 |  65 +++-------
 src/new_fields/Doc.ts                              |   6 +-
 18 files changed, 234 insertions(+), 234 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 22aa74634..0114f82d8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -120,11 +120,11 @@ export namespace Docs {
                 options: { height: 300, backgroundColor: "black" }
             }],
             [DocumentType.IMG, {
-                layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
+                layout: { view: ImageBox, ext: anno },
                 options: {}
             }],
             [DocumentType.WEB, {
-                layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
+                layout: { view: WebBox, ext: anno },
                 options: { height: 300 }
             }],
             [DocumentType.COL, {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a5502577b..063686d58 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -784,8 +784,8 @@ export class DashDocView {
                     addDocTab={self._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={1}
-                    PanelWidth={self._dashDoc![WidthSym]}
-                    PanelHeight={self._dashDoc![HeightSym]}
+                    PanelWidth={self._dashDoc[WidthSym]}
+                    PanelHeight={self._dashDoc[HeightSym]}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
                     parentActive={returnFalse}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d6562492f..93e852bef 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,6 +1,8 @@
 import * as React from 'react';
 import { Doc } from '../../new_fields/Doc';
-import { computed } from 'mobx';
+import { computed, action } from 'mobx';
+import { Cast } from '../../new_fields/Types';
+import { listSpec } from '../../new_fields/Schema';
 
 export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends React.Component<P> {
@@ -11,4 +13,50 @@ export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: D
         }
     }
     return Component;
+}
+
+interface DocAnnotatableProps {
+    Document: Doc;
+    DataDoc?: Doc;
+    fieldKey: string;
+    fieldExt: string;
+    whenActiveChanged: (isActive: boolean) => void;
+    isSelected: () => boolean, renderDepth: number
+}
+export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
+    class Component extends React.Component<P> {
+        //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+        @computed
+        get Document(): T {
+            return schemaCtor(this.props.Document);
+        }
+        _isChildActive = false;
+        @action.bound
+        removeDocument(doc: Doc): boolean {
+            Doc.GetProto(doc).annotationOn = undefined;
+            let value = Cast(this.extensionDoc[this.props.fieldExt], listSpec(Doc), []);
+            let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
+            return index !== -1 && value.splice(index, 1) ? true : false;
+        }
+
+        @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
+
+        @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+
+        // if the moved document is already in this overlay collection nothing needs to be done.
+        // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. 
+        @action.bound
+        moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+            return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false;
+        }
+
+        @action.bound
+        addDocument(doc: Doc): boolean {
+            Doc.GetProto(doc).annotationOn = this.props.Document;
+            return Doc.AddDocToList(this.extensionDoc, this.props.fieldExt, doc);
+        }
+        whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
+        active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+    }
+    return Component;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 5c1960d53..6251d7114 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -86,7 +86,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             this.props.parent.drop(e, de);
             e.stopPropagation();
         }
-    })
+    });
 
     getValue = (value: string): any => {
         let parsed = parseInt(value);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index e8627780d..815b28586 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -72,7 +72,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             this.props.parent.drop(e, de);
             e.stopPropagation();
         }
-    })
+    });
     getValue = (value: string): any => {
         let parsed = parseInt(value);
         if (!isNaN(parsed)) {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 689adc375..06d048383 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -38,6 +38,7 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     ruleProvider: Doc | undefined;
+    children?: never | (() => JSX.Element[]) | React.ReactNode;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d2644480c..adbad5da5 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, faPaintBrush, faTable, faUpload, faFileUpload } from "@fortawesome/free-solid-svg-icons";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble && !this.isAnnotationOverlay) {
+        if (!e.cancelBubble) {
             if (this._hitCluster && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
@@ -339,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return;
+        if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -477,7 +477,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
             ContentScaling: returnOne,
-            ContainingCollectionView: this.props.CollectionView,
+            ContainingCollectionView: this.props.ContainingCollectionView,
             focus: this.focusDocument,
             backgroundColor: returnEmptyString,
             parentActive: this.props.active,
@@ -681,10 +681,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
 
-    private childViews = () => [
-        <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
-        ...this.views
-    ]
+    private childViews = () => {
+        let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
+        return [
+            <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
+            ...children,
+            ...this.views,
+        ];
+    }
     render() {
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         this.props.Document.fitX = this.contentBounds && this.contentBounds.x;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bb787106c..ecdd02b0f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -217,7 +217,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this._downY = y;
             PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
         }
-    })
+    });
 
     @action
     onClick = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9ca77cc8b..cbe4945af 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -218,13 +218,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
             let res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            let tr = this._editorView!.state.tr;
+            let tr = this._editorView.state.tr;
             let flattened: TextSelection[] = [];
             res.map(r => r.map(h => flattened.push(h)));
             let lastSel = Math.min(flattened.length - 1, this._searchIndex);
             flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
             this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-            this._editorView!.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+            this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
         }
     }
 
@@ -232,8 +232,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this._editorView && (this._editorView as any).docView) {
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            let end = this._editorView!.state.doc.nodeSize - 2;
-            this._editorView!.dispatch(this._editorView!.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+            let end = this._editorView.state.doc.nodeSize - 2;
+            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
         }
     }
     setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 71d718b39..97d858f58 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,9 +1,9 @@
-.imageBox-cont {
+.imageBox-cont, .imageBox-cont-interactive {
     padding: 0vw;
-    position: relative;
+    position: absolute;
     text-align: center;
     width: 100%;
-    height: auto;
+    height: 100%;
     max-width: 100%;
     max-height: 100%;
     pointer-events: none;
@@ -11,8 +11,6 @@
 
 .imageBox-cont-interactive {
     pointer-events: all;
-    width: 100%;
-    height: auto;
 }
 
 .imageBox-dot {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a198a0764..9f39eccea 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -13,20 +13,21 @@ import { ComputedField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types';
 import { AudioField, ImageField } from '../../../new_fields/URLField';
 import { RouteStore } from '../../../server/RouteStore';
-import { Utils } from '../../../Utils';
+import { Utils, returnOne, emptyFunction } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../../views/ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
+import { DocAnnotatableComponent } from '../DocComponent';
 import { InkingControl } from '../InkingControl';
 import { documentSchema } from './DocumentView';
 import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
@@ -54,9 +55,10 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>;
 const ImageDocument = makeInterface(pageSchema, documentSchema);
 
 @observer
-export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
+export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
 
-    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(ImageBox, "data", fieldExt); }
+    @observable static _showControls: boolean;
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _downX: number = 0;
     private _downY: number = 0;
@@ -65,10 +67,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     private dropDisposer?: DragManager.DragDropDisposer;
     @observable private hoverActive = false;
 
-    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
-
     protected createDropTarget = (ele: HTMLDivElement) => {
         if (this.dropDisposer) {
             this.dropDisposer();
@@ -381,7 +379,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         return (null);
     }
 
-    render() {
+    @computed
+    get content() {
         // let transform = this.props.ScreenToLocalTransform().inverse();
         let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
         // var [sptX, sptY] = transform.transformPoint(0, 0);
@@ -393,7 +392,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
         // this._curSuffix = "";
         // if (w > 20) {
-        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
         let alts = DocListCast(this.extensionDoc.Alternates);
         let altpaths: string[] = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url));
         let field = this.dataDoc[this.props.fieldKey];
@@ -448,4 +446,30 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 <FaceRectangles document={this.extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
             </div>);
     }
+
+    render() {
+        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
+        return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu}>
+            <CollectionFreeFormView {...this.props}
+                PanelHeight={this.props.PanelHeight}
+                PanelWidth={this.props.PanelWidth}
+                focus={this.props.focus}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocument}
+                CollectionView={undefined}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                chromeCollapsed={true}>
+                {() => [this.content]}
+            </CollectionFreeFormView>
+        </div >);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 57803be1f..9af6d7cad 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,17 +1,21 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import 'react-image-lightbox/style.css';
-import { Doc, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Opt, WidthSym } from "../../../new_fields/Doc";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Cast } from "../../../new_fields/Types";
 import { PdfField } from "../../../new_fields/URLField";
+import { Utils } from '../../../Utils';
 import { KeyCodes } from '../../northstar/utils/KeyCodes';
+import { undoBatch } from '../../util/UndoManager';
 import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { DocComponent } from "../DocComponent";
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { DocAnnotatableComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
 import { PDFViewer } from "../pdf/PDFViewer";
 import { documentSchema } from "./DocumentView";
@@ -19,22 +23,17 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
-import { undoBatch } from '../../util/UndoManager';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ContextMenu } from '../ContextMenu';
-import { Utils } from '../../../Utils';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 
 @observer
-export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
     public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(PDFBox, "data", fieldExt); }
     private _keyValue: string = "";
     private _valueValue: string = "";
     private _scriptValue: string = "";
     private _searchString: string = "";
-    private _isChildActive = false;
     private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title
     private _pdfViewer: PDFViewer | undefined;
     private _keyRef: React.RefObject<HTMLInputElement> = React.createRef();
@@ -46,9 +45,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
     @observable private _pageControls = false;
 
-    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
-
     componentDidMount() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         if (pdfUrl instanceof PdfField) {
@@ -206,7 +202,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
                     isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
-                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
+                    fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
                 {this.settingsPanel()}
             </div>);
     }
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b3cd439aa..48623eaaf 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,8 +1,6 @@
-// .videoBox-container {
-//     .collectionfreeformview-container {
-//         mix-blend-mode: multiply;
-//     }
-// }
+.videoBox-container {
+    pointer-events: all;
+}
 
 .videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
 .videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index feb067d8f..aa9b28118 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -11,7 +11,7 @@ import { Utils, emptyFunction, returnOne } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { documentSchema } from "./DocumentView";
@@ -35,7 +35,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
 library.add(faVideo);
 
 @observer
-export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
     static _youtubeIframeCounter: number = 0;
     private _reactionDisposer?: IReactionDisposer;
     private _youtubeReactionDisposer?: IReactionDisposer;
@@ -43,16 +43,12 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     private _videoRef: HTMLVideoElement | null = null;
     private _youtubeIframeId: number = -1;
     private _youtubeContentCreated = false;
-    private _downX: number = 0;
-    private _downY: number = 0;
     private _isResetClick = 0;
-    private _isChildActive = false;
-    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     @observable _forceCreateYouTubeIFrame = false;
-    @observable static _showControls: boolean;
     @observable _playTimer?: NodeJS.Timeout = undefined;
     @observable _fullScreen = false;
-    @observable public Playing: boolean = false;
+    @observable _playing = false;
+    @observable static _showControls: boolean;
     public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); }
 
     public get player(): HTMLVideoElement | null {
@@ -72,7 +68,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action public Play = (update: boolean = true) => {
-        this.Playing = true;
+        this._playing = true;
         update && this.player && this.player.play();
         update && this._youtubePlayer && this._youtubePlayer.playVideo();
         this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
@@ -85,7 +81,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action public Pause = (update: boolean = true) => {
-        this.Playing = false;
+        this._playing = false;
         update && this.player && this.player.pause();
         update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo();
         this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
@@ -181,7 +177,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
             this._reactionDisposer && this._reactionDisposer();
             this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
-                time => !this.Playing && (vref.currentTime = time), { fireImmediately: true });
+                time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
         }
     }
 
@@ -218,7 +214,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
         let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
         return !field ? <div>Loading</div> :
-            <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
+            <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
                 onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
                 <source src={field.url.href} type="video/mp4" />
                 Not supported.
@@ -246,13 +242,13 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                 this.Pause();
                 return;
             }
-            if (event.data === YT.PlayerState.PLAYING && !this.Playing) this.Play(false);
-            if (event.data === YT.PlayerState.PAUSED && this.Playing) this.Pause(false);
+            if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
+            if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
         });
         let onYoutubePlayerReady = (event: any) => {
             this._reactionDisposer && this._reactionDisposer();
             this._youtubeReactionDisposer && this._youtubeReactionDisposer();
-            this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0));
+            this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0));
             this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
                 let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
                 iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -278,22 +274,16 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         </div>,
         VideoBox._showControls ? (null) : [
             <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
-                <FontAwesomeIcon icon={this.Playing ? "pause" : "play"} size="lg" />
+                <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
             </div>,
-            <div className="collecvideoBoxtionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
+            <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
                 F
             </div>
         ]]);
     }
 
     @action
-    onPlayDown = () => {
-        if (this.Playing) {
-            this.Pause();
-        } else {
-            this.Play();
-        }
-    }
+    onPlayDown = () => this._playing ? this.Pause() : this.Play()
 
     @action
     onFullDown = (e: React.PointerEvent) => {
@@ -342,97 +332,40 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
         return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
             onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
-            src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
-        ></iframe>;
-    }
-
-
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
-        this._setPreviewCursor = func;
+            src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
     }
 
     @action.bound
-    removeDocument(doc: Doc): boolean {
-        Doc.GetProto(doc).annotationOn = undefined;
-        //TODO This won't create the field if it doesn't already exist
-        let targetDataDoc = this.fieldExtensionDoc;
-        let targetField = this.props.fieldExt;
-        let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
-        let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
-        index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
-        index !== -1 && value.splice(index, 1);
-        return true;
-    }
-    // this is called with the document that was dragged and the collection to move it into.
-    // if the target collection is the same as this collection, then the move will be allowed.
-    // otherwise, the document being moved must be able to be removed from its container before
-    // moving it into the target.  
-    @action.bound
-    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
-            return true;
-        }
-        return this.removeDocument(doc) ? addDocument(doc) : false;
-    }
-
-    @action.bound
-    addDocument(doc: Doc): boolean {
+    addDocumentWithTimestamp(doc: Doc): boolean {
         Doc.GetProto(doc).annotationOn = this.props.Document;
         var curTime = NumCast(this.props.Document.currentTimecode, -1);
         curTime !== -1 && (doc.displayTimecode = curTime);
-        Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
-        return true;
-    }
-    whenActiveChanged = (isActive: boolean) => {
-        this._isChildActive = isActive;
-        this.props.whenActiveChanged(isActive);
-    }
-    active = () => {
-        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
-    }
-    onClick = (e: React.MouseEvent) => {
-        this._setPreviewCursor &&
-            e.button === 0 &&
-            Math.abs(e.clientX - this._downX) < 3 &&
-            Math.abs(e.clientY - this._downY) < 3 &&
-            this._setPreviewCursor(e.clientX, e.clientY, false);
-    }
-
-    onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        if ((e.button !== 0 || e.altKey) && this.active()) {
-            this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
-        }
-    }
-    @computed get annotationLayer() {
-        return <CollectionFreeFormView {...this.props}
-            setPreviewCursor={this.setPreviewCursor}
-            PanelHeight={this.props.PanelHeight}
-            PanelWidth={this.props.PanelWidth}
-            focus={this.props.focus}
-            isSelected={this.props.isSelected}
-            select={emptyFunction}
-            active={this.active}
-            ContentScaling={returnOne}
-            whenActiveChanged={this.whenActiveChanged}
-            removeDocument={this.removeDocument}
-            moveDocument={this.moveDocument}
-            addDocument={this.addDocument}
-            CollectionView={undefined}
-            ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-            ruleProvider={undefined}
-            renderDepth={this.props.renderDepth + 1}
-            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-            chromeCollapsed={true}>
-        </CollectionFreeFormView>
+        return Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
     }
 
     render() {
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
-        return (<div className={"videoBox-container"} onClick={this.onClick} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu}>
-            {this.youtubeVideoId ? this.youtubeContent : this.content}
-            {this.annotationLayer}
+        return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu}>
+            <CollectionFreeFormView {...this.props}
+                PanelHeight={this.props.PanelHeight}
+                PanelWidth={this.props.PanelWidth}
+                focus={this.props.focus}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocumentWithTimestamp}
+                CollectionView={undefined}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                chromeCollapsed={true}>
+                {() => [this.youtubeVideoId ? this.youtubeContent : this.content]}
+            </CollectionFreeFormView>
             {this.uIButtons}
         </div >);
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index bb4c5adc9..7c7f9fb83 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,34 +1,36 @@
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { FieldResult, Doc, Field } from "../../../new_fields/Doc";
+import { Doc, FieldResult } from "../../../new_fields/Doc";
 import { HtmlField } from "../../../new_fields/HtmlField";
+import { InkTool } from "../../../new_fields/InkField";
+import { makeInterface } from "../../../new_fields/Schema";
+import { Cast, NumCast } from "../../../new_fields/Types";
 import { WebField } from "../../../new_fields/URLField";
+import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
+import { KeyValueBox } from "./KeyValueBox";
 import "./WebBox.scss";
 import React = require("react");
-import { InkTool } from "../../../new_fields/InkField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from "../../../Utils";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
-import { observable, action, computed } from "mobx";
-import { listSpec } from "../../../new_fields/Schema";
-import { RefField } from "../../../new_fields/RefField";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { updateSourceFile } from "typescript";
-import { KeyValueBox } from "./KeyValueBox";
-import { setReactionScheduler } from "mobx/lib/internal";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Docs } from "../../documents/Documents";
+import { documentSchema } from "./DocumentView";
+import { DocAnnotatableComponent } from "../DocComponent";
 
 library.add(faStickyNote);
 
+type WebDocument = makeInterface<[typeof documentSchema]>;
+const WebDocument = makeInterface(documentSchema);
+
 @observer
-export class WebBox extends React.Component<FieldViewProps> {
+export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
 
-    public static LayoutString() { return FieldView.LayoutString(WebBox); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(WebBox, "data", fieldExt); }
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
@@ -162,8 +164,10 @@ export class WebBox extends React.Component<FieldViewProps> {
             e.stopPropagation();
         }
     }
-    render() {
-        let field = this.props.Document[this.props.fieldKey];
+
+    @computed
+    get content() {
+        let field = this.dataDoc[this.props.fieldKey];
         let view;
         if (field instanceof HtmlField) {
             view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
@@ -189,4 +193,29 @@ export class WebBox extends React.Component<FieldViewProps> {
                 {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
             </>);
     }
+    render() {
+        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
+        return (<div className={"imageBox-container"} >
+            <CollectionFreeFormView {...this.props}
+                PanelHeight={this.props.PanelHeight}
+                PanelWidth={this.props.PanelWidth}
+                focus={this.props.focus}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocument}
+                CollectionView={undefined}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                chromeCollapsed={true}>
+                {() => [this.content]}
+            </CollectionFreeFormView>
+        </div >);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 5a07b88d9..ad6240c70 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -11,7 +11,7 @@ import "./Annotation.scss";
 
 interface IAnnotationProps {
     anno: Doc;
-    fieldExtensionDoc: Doc;
+    extensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     focus: (doc: Doc) => void;
@@ -29,7 +29,7 @@ interface IRegionAnnotationProps {
     y: number;
     width: number;
     height: number;
-    fieldExtensionDoc: Doc;
+    extensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     document: Doc;
@@ -66,12 +66,12 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     deleteAnnotation = () => {
-        let annotation = DocListCast(this.props.fieldExtensionDoc.annotations);
+        let annotation = DocListCast(this.props.extensionDoc.annotations);
         let group = FieldValue(Cast(this.props.document.group, Doc));
         if (group) {
             if (annotation.indexOf(group) !== -1) {
                 let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
-                this.props.fieldExtensionDoc.annotations = new List<Doc>(newAnnotations);
+                this.props.extensionDoc.annotations = new List<Doc>(newAnnotations);
             }
 
             DocListCast(group.annotations).forEach(anno => anno.delete = true);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 366861144..d0759d207 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -6,7 +6,7 @@ import { Dictionary } from "typescript-collections";
 import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
+import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils";
@@ -23,19 +23,24 @@ import Annotation from "./Annotation";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
+import { DocAnnotatableComponent } from "../DocComponent";
+import { documentSchema } from "../nodes/DocumentView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 
 pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
+type PdfDocument = makeInterface<[typeof documentSchema]>;
+const PdfDocument = makeInterface(documentSchema);
 
 interface IViewerProps {
     pdf: Pdfjs.PDFDocumentProxy;
     url: string;
-    Document: Doc;
-    DataDoc?: Doc;
-    fieldExtensionDoc: Doc;
     fieldKey: string;
     fieldExt: string;
+    Document: Doc;
+    DataDoc?: Doc;
+    ContainingCollectionView: Opt<CollectionView>;
+    extensionDoc: Doc;
     PanelWidth: () => number;
     PanelHeight: () => number;
     ContentScaling: () => number;
@@ -52,7 +57,6 @@ interface IViewerProps {
     addDocument?: (doc: Doc) => boolean;
     setPdfViewer: (view: PDFViewer) => void;
     ScreenToLocalTransform: () => Transform;
-    ContainingCollectionView: Opt<CollectionView>;
     whenActiveChanged: (isActive: boolean) => void;
 }
 
@@ -60,7 +64,7 @@ interface IViewerProps {
  * Handles rendering and virtualization of the pdf
  */
 @observer
-export class PDFViewer extends React.Component<IViewerProps> {
+export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number, height: number }[] = [];
     @observable private _annotations: Doc[] = [];
@@ -79,7 +83,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     private _pdfViewer: any;
     private _retries = 0; // number of times tried to create the PDF viewer 
-    private _isChildActive = false;
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
@@ -97,7 +100,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _coverPath: any;
 
     @computed get allAnnotations() {
-        return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
+        return DocListCast(this.props.extensionDoc.annotations).filter(
             anno => this._script.run({ this: anno }, console.log, true).result);
     }
 
@@ -186,7 +189,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         await this.initialLoad();
 
         this._annotationReactionDisposer = reaction(
-            () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations),
+            () => this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations),
             annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
             { fireImmediately: true });
 
@@ -530,7 +533,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     highlight = (color: string) => {
         // creates annotation documents for current highlights
         let annotationDoc = this.makeAnnotationDocument(color);
-        annotationDoc && Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
+        annotationDoc && Doc.AddDocToList(this.props.extensionDoc, this.props.fieldExt, annotationDoc);
         return annotationDoc;
     }
 
@@ -570,40 +573,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
         DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
     }
 
-    // this is called with the document that was dragged and the collection to move it into.
-    // if the target collection is the same as this collection, then the move will be allowed.
-    // otherwise, the document being moved must be able to be removed from its container before
-    // moving it into the target.  
-    @action.bound
-    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
-            return true;
-        }
-        return this.removeDocument(doc) ? addDocument(doc) : false;
-    }
-    @action.bound
-    addDocument(doc: Doc): boolean {
-        Doc.GetProto(doc).annotationOn = this.props.Document;
-        Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc);
-        return true;
-    }
-    @action.bound
-    removeDocument(doc: Doc): boolean {
-        Doc.GetProto(doc).annotationOn = undefined;
-        let targetDataDoc = this.props.fieldExtensionDoc;
-        let targetField = this.props.fieldExt;
-        let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
-        let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
-        index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
-        index !== -1 && value.splice(index, 1);
-        return true;
-    }
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform();
     }
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
-        this._setPreviewCursor = func;
-    }
     onClick = (e: React.MouseEvent) => {
         this._setPreviewCursor &&
             e.button === 0 &&
@@ -611,13 +583,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
             Math.abs(e.clientY - this._downY) < 3 &&
             this._setPreviewCursor(e.clientX, e.clientY, false);
     }
-    whenActiveChanged = (isActive: boolean) => {
-        this._isChildActive = isActive;
-        this.props.whenActiveChanged(isActive);
-    }
-    active = () => {
-        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
-    }
+
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
+
 
     getCoverImage = () => {
         if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
@@ -632,7 +600,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
     }
 
-
     @action
     onZoomWheel = (e: React.WheelEvent) => {
         e.stopPropagation();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 418863bcc..eb752f8c6 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -332,8 +332,10 @@ export namespace Doc {
         return Array.from(results);
     }
 
-    export function IndexOf(toFind: Doc, list: Doc[]) {
-        return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
+    export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
+        let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
+        index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
+        return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
     }
     export function RemoveDocFromList(listDoc: Doc, key: string, doc: Doc) {
         if (listDoc[key] === undefined) {
-- 
cgit v1.2.3-70-g09d2


From d95d46e90851141bed17dd30c103be5d0e1e60ab Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 11 Oct 2019 13:43:48 -0400
Subject: search fixes for pdfs

---
 src/client/views/GlobalKeyHandler.ts               |  2 ++
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 36 ++++++++++++++++++++--
 src/client/views/nodes/QueryBox.tsx                |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  7 +++--
 src/server/index.ts                                |  1 -
 6 files changed, 43 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 6815ff926..557b3e366 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -163,6 +163,8 @@ export default class KeyManager {
                 }
                 break;
             case "f":
+                stopPropagation = false;
+                preventDefault = false;
                 MainView.Instance.isSearchVisible = !MainView.Instance.isSearchVisible;
                 MainView.Instance.flyoutWidth = MainView.Instance.isSearchVisible ? 400 : 0;
                 PromiseValue(Cast(CurrentUserUtils.UserDocument.searchBox, Doc)).then(pv => pv && (pv.treeViewOpen = (MainView.Instance.flyoutWidth > 0)));
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 06d048383..fdbe5339d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -275,7 +275,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             let full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName };
                             let pathname = Utils.prepend(file.path);
                             Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
-                                doc && (doc.fileUpload = path.basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
+                                doc && (Doc.GetProto(doc).fileUpload = path.basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""));
                                 doc && this.props.addDocument(doc);
                             });
                         }));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 9af6d7cad..02a82e0ed 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, runInAction } from 'mobx';
+import { action, observable, runInAction, reaction, IReactionDisposer } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
@@ -36,20 +36,34 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     private _searchString: string = "";
     private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title
     private _pdfViewer: PDFViewer | undefined;
+    private _searchRef: React.RefObject<HTMLInputElement> = React.createRef();
     private _keyRef: React.RefObject<HTMLInputElement> = React.createRef();
     private _valueRef: React.RefObject<HTMLInputElement> = React.createRef();
     private _scriptRef: React.RefObject<HTMLInputElement> = React.createRef();
+    private _selectReaction: IReactionDisposer | undefined;
 
     @observable private _searching: boolean = false;
     @observable private _flyout: boolean = false;
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
     @observable private _pageControls = false;
 
+    componentWillUnmount() {
+        this._selectReaction && this._selectReaction();
+    }
     componentDidMount() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         if (pdfUrl instanceof PdfField) {
             Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf));
         }
+        this._selectReaction = reaction(() => this.props.isSelected(),
+            () => {
+                if (this.props.isSelected()) {
+                    document.removeEventListener("keydown", this.onKeyDown);
+                    document.addEventListener("keydown", this.onKeyDown);
+                } else {
+                    document.removeEventListener("keydown", this.onKeyDown);
+                }
+            }, { fireImmediately: true });
     }
     loaded = (nw: number, nh: number, np: number) => {
         this.dataDoc.numPages = np;
@@ -65,6 +79,22 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
     public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
 
+    @undoBatch
+    onKeyDown = action((e: KeyboardEvent) => {
+        if (e.key === "f" && e.ctrlKey) {
+            this._searching = true;
+            setTimeout(() => this._searchRef.current && this._searchRef.current.focus(), 100);
+            e.stopImmediatePropagation();
+            e.preventDefault();
+        }
+        if (e.key === "PageDown" || e.key === "ArrowDown" || e.key === "ArrowRight") {
+            this.forwardPage();
+        }
+        if (e.key === "PageUp" || e.key === "ArrowUp" || e.key === "ArrowLeft") {
+            this.backPage();
+        }
+    })
+
     @undoBatch
     @action
     private applyFilter = () => {
@@ -109,7 +139,9 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
                 onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none", position: "absolute", width: "100%", height: "100%", zIndex: 1, pointerEvents: "none" }}>
                 <div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                     <button className="pdfBox-overlayButton" title="Open Search Bar" />
-                    <input className="pdfBox-searchBar" placeholder="Search" onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
+                    <input className="pdfBox-searchBar" placeholder="Search" autoFocus={true} ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => {
+                        e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey);
+                    }} />
                     <button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
                         <FontAwesomeIcon icon="search" size="sm" color="white" /></button>
                     <button className="pdfBox-prevIcon " title="Previous Annotation" onClick={e => this.prevAnnotation()} >
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 7a8e0df63..b8c464350 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -45,6 +45,6 @@ export class QueryBox extends React.Component<FieldViewProps> {
     render() {
         return <div style={{ width: "100%", height: "100%", position: "absolute", pointerEvents: "all" }}>
             <FilterBox></FilterBox>
-        </div>
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d0759d207..1bae6128c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -126,7 +126,9 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
             }
         }, { fireImmediately: true });
 
-        this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => (this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(), { fireImmediately: true });
+        this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
+            () => (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(),
+            { fireImmediately: true });
         this._reactionDisposer = reaction(
             () => this.props.Document.scrollY,
             (scrollY) => {
@@ -655,7 +657,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     marqueeing = () => this._marqueeing;
     visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
     render() {
-        return (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
+        return (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")}
+            onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
             {this.pdfViewerDiv}
             {this.annotationLayer}
             {this.standinViews}
diff --git a/src/server/index.ts b/src/server/index.ts
index 62938b9c7..86c226a21 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -213,7 +213,6 @@ const solrURL = "http://localhost:8983/solr/#/dash";
 
 app.get("/textsearch", async (req, res) => {
     let q = req.query.q;
-    console.log("TEXTSEARCH " + q);
     if (q === undefined) {
         res.send([]);
         return;
-- 
cgit v1.2.3-70-g09d2


From c03012fe8c9b77ddf3b7e6c589339eb9a0d967e6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 12 Oct 2019 01:33:20 -0400
Subject: working version of new docked sidebar + configurable bottom buttons 
 by drag/drop

---
 src/client/views/GlobalKeyHandler.ts               |  18 ++-
 src/client/views/Main.scss                         |  13 +-
 src/client/views/MainView.tsx                      | 157 ++++++++++-----------
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../nodes/CollectionFreeFormDocumentView.scss      |   2 +
 src/client/views/nodes/DocumentView.tsx            |   1 +
 src/client/views/nodes/DragBox.tsx                 |  12 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 .../authentication/models/current_user_utils.ts    |  45 +++---
 9 files changed, 126 insertions(+), 126 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 9d239d0bf..82f5a573c 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -164,29 +164,27 @@ export default class KeyManager {
                 }
                 break;
             case "c":
-                if (MainView.Instance.flyoutWidth > 0) {
+                PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
+                if (MainView.Instance.flyoutWidth === 75) {
                     MainView.Instance.flyoutWidth = 0;
-                    PromiseValue(Cast(CurrentUserUtils.UserDocument.Library, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 } else {
-                    MainView.Instance.flyoutWidth = 400;
-                    PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
+                    MainView.Instance.flyoutWidth = 75;
                 }
                 break;
             case "l":
-                if (MainView.Instance.flyoutWidth > 0) {
+                PromiseValue(Cast(CurrentUserUtils.UserDocument.Library, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
+                if (MainView.Instance.flyoutWidth === 250) {
                     MainView.Instance.flyoutWidth = 0;
                 } else {
-                    MainView.Instance.flyoutWidth = 400;
-                    PromiseValue(Cast(CurrentUserUtils.UserDocument.Library, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
+                    MainView.Instance.flyoutWidth = 250;
                 }
                 break;
             case "f":
-                if (MainView.Instance.flyoutWidth > 0) {
+                PromiseValue(Cast(CurrentUserUtils.UserDocument.Search, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
+                if (MainView.Instance.flyoutWidth === 400) {
                     MainView.Instance.flyoutWidth = 0;
-                    PromiseValue(Cast(CurrentUserUtils.UserDocument.Library, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 } else {
                     MainView.Instance.flyoutWidth = 400;
-                    PromiseValue(Cast(CurrentUserUtils.UserDocument.Search, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 }
                 break;
             case "o":
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4cd860da2..705da7b35 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -211,14 +211,21 @@ button:hover {
 }
 
 #add-options-content {
-    display: table;
+    display: flex;
     opacity: 1;
     margin: 0;
     padding: 0;
     position: relative;
     float: right;
-    bottom: 0.3em;
-    margin-bottom: -1.68em;
+    .mainView-docBtn {
+        position:relative;
+        margin-right: 10px;
+        width: 35px;
+        height: 35px;
+    }
+    .collectionFreeFormDocumentView-container {
+        position: relative;
+    }
 }
 
 ul#add-options-list {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6f60de1c4..1fa231659 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faChevronRight, faEllipsisV } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -12,15 +12,16 @@ import { Id } from '../../new_fields/FieldSymbols';
 import { InkTool } from '../../new_fields/InkField';
 import { List } from '../../new_fields/List';
 import { listSpec } from '../../new_fields/Schema';
-import { BoolCast, Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types';
+import { ScriptField } from '../../new_fields/ScriptField';
+import { BoolCast, Cast, FieldValue, PromiseValue, StrCast, NumCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, returnFalse, Utils } from '../../Utils';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../Utils';
+import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
-import { ClientUtils } from '../util/ClientUtils';
 import { DictationManager } from '../util/DictationManager';
-import { SetupDrag } from '../util/DragManager';
+import { SetupDrag, DragManager } from '../util/DragManager';
 import { HistoryUtil } from '../util/History';
 import SharingManager from '../util/SharingManager';
 import { Transform } from '../util/Transform';
@@ -35,13 +36,10 @@ import { InkingControl } from './InkingControl';
 import "./Main.scss";
 import MainViewModal from './MainViewModal';
 import { DocumentView } from './nodes/DocumentView';
+import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
-import { FilterBox } from './search/FilterBox';
-import { OverlayView } from './OverlayView';
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
-import { CollectionStackingView } from './collections/CollectionStackingView';
-import { ScriptField } from '../../new_fields/ScriptField';
+import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
 
 @observer
 export class MainView extends React.Component {
@@ -49,6 +47,7 @@ export class MainView extends React.Component {
     @observable addMenuToggle = React.createRef<HTMLInputElement>();
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
+    private dropDisposer?: DragManager.DragDropDisposer;
 
     @observable private dictationState = DictationManager.placeholder;
     @observable private dictationSuccessState: boolean | undefined = undefined;
@@ -164,6 +163,7 @@ export class MainView extends React.Component {
         //close presentation 
         window.removeEventListener("pointerdown", this.globalPointerDown);
         window.removeEventListener("pointerup", this.globalPointerUp);
+        this.dropDisposer && this.dropDisposer();
     }
 
     constructor(props: Readonly<{}>) {
@@ -287,18 +287,15 @@ export class MainView extends React.Component {
         let workspaces: FieldResult<Doc>;
         let freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
-        let mainDoc = Docs.Create.DockDocument([this.userDoc, freeformDoc], JSON.stringify(dockingLayout), {}, id);
+        let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id);
         if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) {
-            const list = Cast((workspaces).data, listSpec(Doc));
-            if (list) {
-                if (!this.userDoc.linkManagerDoc) {
-                    let linkManagerDoc = new Doc();
-                    linkManagerDoc.allLinks = new List<Doc>([]);
-                    this.userDoc.linkManagerDoc = linkManagerDoc;
-                }
-                list.push(mainDoc);
-                mainDoc.title = `Workspace ${list.length}`;
+            if (!this.userDoc.linkManagerDoc) {
+                let linkManagerDoc = new Doc();
+                linkManagerDoc.allLinks = new List<Doc>([]);
+                this.userDoc.linkManagerDoc = linkManagerDoc;
             }
+            Doc.AddDocToList(workspaces, "data", mainDoc);
+            mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`;
         }
         // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
         setTimeout(() => {
@@ -350,6 +347,10 @@ export class MainView extends React.Component {
         return true;
     }
 
+    drop = action((e: Event, de: DragManager.DropEvent) => {
+        (de.data as DragManager.DocumentDragData).draggedDocuments.map(doc => Doc.AddDocToList(CurrentUserUtils.UserDocument, "docButtons", doc));
+    })
+
     onDrop = (e: React.DragEvent<HTMLDivElement>) => {
         e.preventDefault();
         e.stopPropagation();
@@ -460,13 +461,14 @@ export class MainView extends React.Component {
     }
     @computed
     get flyout() {
-        let sidebar: FieldResult<Field>;
-        if (!this.userDoc || !((sidebar = this.userDoc.sidebarContainer) instanceof Doc)) {
+        let sidebarContent = this.userDoc && this.userDoc.sidebarContainer;
+        if (!(sidebarContent instanceof Doc)) {
             return (null);
         }
         (Cast(CurrentUserUtils.UserDocument.libraryButtons, Doc) as Doc).columnWidth = this.flyoutWidthFunc() / 3 - 30;
+        let buttonBarHeight = 85;
         return <div style={{ display: "flex", flexDirection: "column", position: "absolute", width: "100%", height: "100%" }}>
-            <div style={{ position: "relative", height: "85px", width: "100%" }}>
+            <div style={{ position: "relative", height: `${buttonBarHeight}px`, width: "100%" }}>
                 <DocumentView
                     Document={Cast(CurrentUserUtils.UserDocument.libraryButtons, Doc) as Doc}
                     DataDoc={undefined}
@@ -492,9 +494,9 @@ export class MainView extends React.Component {
                     getScale={returnOne}>
                 </DocumentView>
             </div>
-            <div style={{ position: "relative", height: "calc(100% - 80px)", width: "100%" }}>
+            <div style={{ position: "relative", height: `calc(100% - ${buttonBarHeight}px)`, width: "100%", overflow: "auto" }}>
                 <DocumentView
-                    Document={this.isSearchVisible ? Cast(CurrentUserUtils.UserDocument.searchBox, Doc) as Doc : sidebar}
+                    Document={sidebarContent}
                     DataDoc={undefined}
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
@@ -598,65 +600,69 @@ export class MainView extends React.Component {
         DocServer.setFieldWriteMode("viewType", mode2);
     }
 
+    protected createDropTarget = (ele: HTMLLabelElement) => { //used for stacking and masonry view
+        this.dropDisposer && this.dropDisposer();
+        if (ele) {
+            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+        }
+    }
 
     @observable private _colorPickerDisplay = false;
     /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
     nodesMenu() {
-        let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
-
-        let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
-        let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
-        let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
-        let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
-        let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
-        let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" }));
-        let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
         // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
         // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" }));
 
         // let googlePhotosSearch = () => GooglePhotosClientUtils.CollectionFromSearch(Docs.Create.MasonryDocument, { included: [GooglePhotosClientUtils.ContentCategories.LANDSCAPES] });
 
-        let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc | Promise<Doc>][] = [
-            //[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
-            //[React.createRef<HTMLDivElement>(), "tv", "Add Presentation Trail", addPresNode],
-            //[React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
-            //[React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
-            //[React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
-            // [React.createRef<HTMLDivElement>(), "object-group", "Test Google Photos Search", googlePhotosSearch],
-            //[React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
-            //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
-        ];
-        //if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
-
         return < div id="add-nodes-menu" style={{ left: (this.flyoutTranslate ? this.flyoutWidth : 0) + 20, bottom: 20 }} >
 
             <input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
-            <label htmlFor="add-menu-toggle" style={{ marginTop: 2 }} title="Close Menu"><p>+</p></label>
+            <label htmlFor="add-menu-toggle" ref={this.createDropTarget} title="Close Menu"><p>+</p></label>
 
             <div id="add-options-content">
-                <ul id="add-options-list">
-                    {/* <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li> */}
-                    <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
-                    <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
-                    {btns.map(btn =>
-                        <li key={btn[1]} ><div ref={btn[0]}>
-                            <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
-                                <FontAwesomeIcon icon={btn[1]} size="sm" />
-                            </button>
-                        </div></li>)}
-                    {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
-                    <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
-                        <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
-                            <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
-                        </div>
-                    </div></button></li>
-                    <li key="ink" style={{ paddingRight: "6px" }}><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button></li>
-                    <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button></li>
-                    <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button></li>
-                    <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button></li>
-                    <li key="inkControls"><InkingControl /></li>
-                    <li key="logout"><button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}</button></li>
-                </ul>
+                {/* <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li> */}
+                <button key="undo" className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
+                <button key="redo" className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
+
+                {DocListCast(CurrentUserUtils.UserDocument.docButtons).map(doc => <div className="mainView-docBtn" key={StrCast(doc.title)} >
+                    <DocumentView
+                        Document={doc}
+                        DataDoc={undefined}
+                        addDocument={undefined}
+                        addDocTab={this.addDocTabFunc}
+                        pinToPres={emptyFunction}
+                        removeDocument={undefined}
+                        ruleProvider={undefined}
+                        onClick={undefined}
+                        ScreenToLocalTransform={Transform.Identity}
+                        ContentScaling={() => 35 / NumCast(doc.nativeWidth, 35)}
+                        PanelWidth={() => 35}
+                        PanelHeight={() => 35}
+                        renderDepth={0}
+                        focus={emptyFunction}
+                        backgroundColor={returnEmptyString}
+                        parentActive={returnTrue}
+                        whenActiveChanged={emptyFunction}
+                        bringToFront={emptyFunction}
+                        ContainingCollectionView={undefined}
+                        ContainingCollectionDoc={undefined}
+                        zoomToScale={emptyFunction}
+                        getScale={returnOne}>
+                    </DocumentView>
+                </div>)}
+                {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
+                <button key="color" className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
+                    <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
+                        <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
+                    </div>
+                </div></button>
+                <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button>
+                <button key="pen" onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button>
+                <button key="marker" onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
+                <button key="eraser" onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button>
+                <InkingControl key="inkControls" />
+                <button key="logout" onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}</button>
             </div>
         </div >;
     }
@@ -668,16 +674,6 @@ export class MainView extends React.Component {
         this._colorPickerDisplay = close ? false : !this._colorPickerDisplay;
     }
 
-    /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
-    @computed
-    get miscButtons() {
-        return [
-            //this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
-        ];
-
-    }
-
-    @observable isSearchVisible = false;
     @action.bound
     toggleSearch = () => {
         PromiseValue(Cast(CurrentUserUtils.UserDocument.Search, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
@@ -716,7 +712,6 @@ export class MainView extends React.Component {
                 <PreviewCursor />
                 <ContextMenu />
                 {this.nodesMenu()}
-                {this.miscButtons}
                 <PDFMenu />
                 <OverlayView />
             </div >
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6f5587b5a..a325e86c6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -188,7 +188,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-            if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+            if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking && this.props.document !== CurrentUserUtils.UserDocument.workspaces) {
                 ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" });
                 ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
                 ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index c0d9e1267..af9232c2f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -2,4 +2,6 @@
     transform-origin: left top;
     position: absolute;
     background-color: transparent;
+    top:0;
+    left:0;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 758222e52..20920a9b8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -614,6 +614,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DataDoc={this.props.DataDoc} />);
     }
     render() {
+        if (!this.props.Document) return (null);
         let animDims = this.props.Document.animateToDimensions ? Array.from(Cast(this.props.Document.animateToDimensions, listSpec("number"))!) : undefined;
         const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
         const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
diff --git a/src/client/views/nodes/DragBox.tsx b/src/client/views/nodes/DragBox.tsx
index 8429382e3..ab3368b94 100644
--- a/src/client/views/nodes/DragBox.tsx
+++ b/src/client/views/nodes/DragBox.tsx
@@ -51,9 +51,15 @@ export class DragBox extends DocComponent<FieldViewProps, DragDocument>(DragDocu
             const onDragStart = this.Document.onDragStart;
             e.stopPropagation();
             e.preventDefault();
-            let res = onDragStart && onDragStart.script.run({ this: this.props.Document }).result;
-            let doc = (res as Doc) || Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" });
-            DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+            DragManager.StartDocumentDrag([this._mainCont.current!], new DragManager.DocumentDragData([this.props.Document]), e.clientX, e.clientY, {
+                finishDrag: (dropData) => {
+                    let res = onDragStart && onDragStart.script.run({ this: this.props.Document }).result;
+                    let doc = (res as Doc) || Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" });
+                    dropData.droppedDocuments = [doc];
+                },
+                handlers: { dragComplete: emptyFunction },
+                hideSource: false
+            });
         }
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 02a82e0ed..63b412a23 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -93,7 +93,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         if (e.key === "PageUp" || e.key === "ArrowUp" || e.key === "ArrowLeft") {
             this.backPage();
         }
-    })
+    });
 
     @undoBatch
     @action
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e1bb91838..7408fd92b 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -127,46 +127,38 @@ export class CurrentUserUtils {
             Search.targetContainer = doc.sidebarContainer;
             Search.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.searchBox");
 
-            let createCollection = Docs.Create.DragboxDocument({ width: 35, height: 35, title: "Collection", icon: "folder" });
-            let createWebPage = Docs.Create.DragboxDocument({ width: 35, height: 35, title: "Web Page", icon: "globe-asia" });
+            let createCollection = Docs.Create.DragboxDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "folder" });
+            let createWebPage = Docs.Create.DragboxDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Web Page", icon: "globe-asia" });
             createWebPage.onDragStart = ScriptField.MakeFunction('Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })');
-            let createCatImage = Docs.Create.DragboxDocument({ width: 35, height: 35, title: "Image", icon: "cat" });
+            let createCatImage = Docs.Create.DragboxDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Image", icon: "cat" });
             createCatImage.onDragStart = ScriptField.MakeFunction('Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })');
-            let createButton = Docs.Create.DragboxDocument({ width: 35, height: 35, title: "Button", icon: "bolt" });
+            let createButton = Docs.Create.DragboxDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Button", icon: "bolt" });
             createButton.onDragStart = ScriptField.MakeFunction('Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })');
-            let createPresentation = Docs.Create.DragboxDocument({ width: 35, height: 35, title: "Presentation", icon: "tv" });
+            let createPresentation = Docs.Create.DragboxDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Presentation", icon: "tv" });
             createPresentation.onDragStart = ScriptField.MakeFunction('Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })');
-            let createFolderImport = Docs.Create.DragboxDocument({ width: 35, height: 35, title: "Import Folder", icon: "cloud-upload-alt" });
+            let createFolderImport = Docs.Create.DragboxDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Import Folder", icon: "cloud-upload-alt" });
             createFolderImport.onDragStart = ScriptField.MakeFunction('Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })');
             const creators = Docs.Create.MasonryDocument([createCollection, createWebPage, createCatImage, createButton, createPresentation, createFolderImport], { width: 500, height: 50, columnWidth: 35, chromeStatus: "disabled", title: "buttons" });
             Create.targetContainer = doc.sidebarContainer;
             Create.creators = creators;
             Create.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.creators");
 
-            const buttons = Docs.Create.StackingDocument([Search, Library, Create], { width: 500, height: 80, chromeStatus: "disabled", title: "buttons" });
-            buttons.sectionFilter = "title";
-            buttons.boxShadow = "0 0";
-            buttons.ignoreClick = true;
-            buttons.hideHeadings = true;
-            doc.libraryButtons = buttons;
+            const libraryButtons = Docs.Create.StackingDocument([Search, Library, Create], { width: 500, height: 80, chromeStatus: "disabled", title: "buttons" });
+            libraryButtons.sectionFilter = "title";
+            libraryButtons.boxShadow = "0 0";
+            libraryButtons.ignoreClick = true;
+            libraryButtons.hideHeadings = true;
+            libraryButtons.backgroundColor = "lightgrey";
 
+            doc.libraryButtons = libraryButtons;
             doc.Library = Library;
             doc.Create = Create;
             doc.Search = Search;
-            (Library.onClick as ScriptField).script.run({ this: Library });
-            //(doc.sidebarContainer as Doc).proto = library;
         }
-        PromiseValue(Cast(doc.libraryButtons, Doc)).then(libraryButtons => {
-            if (libraryButtons) {
-                libraryButtons.backgroundColor = "lightgrey";
-            }
-        });
-
-        PromiseValue(Cast(doc.sidebar, Doc)).then(sidebar => {
-            if (sidebar) {
-                sidebar.backgroundColor = "lightgrey";
-            }
-        });
+        PromiseValue(Cast(doc.libraryButtons, Doc)).then(libraryButtons => { });
+        PromiseValue(Cast(doc.Library, Doc)).then(library => library && library.library && library.targetContainer && (library.onClick as ScriptField).script.run({ this: library }));
+        PromiseValue(Cast(doc.Create, Doc)).then(async create => create && create.creators && create.targetContainer);
+        PromiseValue(Cast(doc.Search, Doc)).then(async search => search && search.searchBox && search.targetContainer);
 
         if (doc.overlays === undefined) {
             const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" });
@@ -178,8 +170,7 @@ export class CurrentUserUtils {
             PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" })));
         }
 
-        StrCast(doc.title).indexOf("@") !== -1 && (doc.title = (StrCast(doc.title).split("@")[0] + "'s Library").toUpperCase());
-        StrCast(doc.title).indexOf("'s Library") !== -1 && (doc.title = StrCast(doc.title).toUpperCase());
+        doc.title = "DOCUMENTS";
         doc.backgroundColor = "#eeeeee";
         doc.width = 100;
         doc.preventTreeViewOpen = true;
-- 
cgit v1.2.3-70-g09d2

