From 1ebf40ba117ec063ff58ee750e6cc57aa3f3abfb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 3 Apr 2019 15:49:42 -0400
Subject: fixed pen ink.  fixed alias dropping.  switched PDFs to use svg

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

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

diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 316d20c9d..2b0689800 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -82,7 +82,8 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
         if (de.data instanceof DragManager.DocumentDragData) {
             if (de.data.aliasOnDrop) {
                 [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
-                    de.data.draggedDocuments.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null));
+                    de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
+                        draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
             }
             let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true);
             if (added && de.data.removeDocument && !de.data.aliasOnDrop) {
-- 
cgit v1.2.3-70-g09d2


From b78aff5115862cbfa5e704422cb738aa7fd73c64 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 4 Apr 2019 14:13:29 -0400
Subject: fixed a number of smaller issues related to linking

---
 src/client/northstar/dash-nodes/HistogramBox.scss  |   5 +
 src/client/util/DragManager.ts                     | 533 +++++++-------
 src/client/util/SelectionManager.ts                |  74 +-
 src/client/views/DocumentDecorations.scss          |  61 +-
 src/client/views/DocumentDecorations.tsx           |  81 ++-
 src/client/views/InkingCanvas.scss                 |   2 +
 src/client/views/Main.scss                         |   7 +
 .../views/collections/CollectionDockingView.scss   |   2 +-
 .../views/collections/CollectionPDFView.scss       |   2 +
 .../views/collections/CollectionVideoView.scss     |   2 +
 .../views/collections/CollectionViewBase.tsx       |  20 +
 .../CollectionFreeFormLinkView.tsx                 |   8 +-
 .../CollectionFreeFormLinksView.scss               |   2 +
 .../collectionFreeForm/CollectionFreeFormView.scss |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  13 +-
 .../collectionFreeForm/MarqueeView.scss            |   2 +
 .../collectionFreeForm/PreviewCursor.scss          |   4 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +
 src/client/views/nodes/DocumentView.scss           |   2 +
 src/client/views/nodes/PDFBox.scss                 |   4 +
 src/client/views/nodes/WebBox.scss                 |   2 +
 src/fields/Document.ts                             | 773 +++++++++++----------
 22 files changed, 882 insertions(+), 727 deletions(-)

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

diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
index b11840a65..94da36e29 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -1,6 +1,8 @@
 .histogrambox-container {
     padding: 0vw;
     position: absolute;
+    top: 0;
+    left:0;
     text-align: center;
     width: 100%;
     height: 100%;
@@ -8,6 +10,7 @@
   }
   .histogrambox-xaxislabel {
     position:absolute;
+    left:0;
     width:100%;
     text-align: center;
     bottom:0;
@@ -19,11 +22,13 @@
     position:absolute;
     height:100%;
     width: 25px;
+    left:0;
     bottom:0;
     background: lightgray;
   }
   .histogrambox-yaxislabel-text {
     position:absolute;
+    left:0;
     transform-origin: left;
     transform: rotate(-90deg);
     text-align: center;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c0f482e18..e8f8cce7c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -8,304 +8,307 @@ import { CollectionView } from "../views/collections/CollectionView";
 import { DocumentView } from "../views/nodes/DocumentView";
 
 export function setupDrag(
-  _reference: React.RefObject<HTMLDivElement>,
-  docFunc: () => Document,
-  removeFunc: (containingCollection: CollectionView) => void = () => {}
+    _reference: React.RefObject<HTMLDivElement>,
+    docFunc: () => Document,
+    removeFunc: (containingCollection: CollectionView) => void = () => { }
 ) {
-  let onRowMove = action(
-    (e: PointerEvent): void => {
-      e.stopPropagation();
-      e.preventDefault();
+    let onRowMove = action(
+        (e: PointerEvent): void => {
+            e.stopPropagation();
+            e.preventDefault();
 
-      document.removeEventListener("pointermove", onRowMove);
-      document.removeEventListener("pointerup", onRowUp);
-      var dragData = new DragManager.DocumentDragData([docFunc()]);
-      dragData.removeDocument = removeFunc;
-      DragManager.StartDocumentDrag([_reference.current!], dragData);
-    }
-  );
-  let onRowUp = action(
-    (e: PointerEvent): void => {
-      document.removeEventListener("pointermove", onRowMove);
-      document.removeEventListener("pointerup", onRowUp);
-    }
-  );
-  let onItemDown = (e: React.PointerEvent) => {
-    // if (this.props.isSelected() || this.props.isTopMost) {
-    if (e.button == 0) {
-      e.stopPropagation();
-      if (e.shiftKey) {
-        CollectionDockingView.Instance.StartOtherDrag([docFunc()], e);
-      } else {
-        document.addEventListener("pointermove", onRowMove);
-        document.addEventListener("pointerup", onRowUp);
-      }
-    }
-    //}
-  };
-  return onItemDown;
+            document.removeEventListener("pointermove", onRowMove);
+            document.removeEventListener("pointerup", onRowUp);
+            var dragData = new DragManager.DocumentDragData([docFunc()]);
+            dragData.removeDocument = removeFunc;
+            DragManager.StartDocumentDrag([_reference.current!], dragData);
+        }
+    );
+    let onRowUp = action(
+        (e: PointerEvent): void => {
+            document.removeEventListener("pointermove", onRowMove);
+            document.removeEventListener("pointerup", onRowUp);
+        }
+    );
+    let onItemDown = (e: React.PointerEvent) => {
+        // if (this.props.isSelected() || this.props.isTopMost) {
+        if (e.button == 0) {
+            e.stopPropagation();
+            if (e.shiftKey) {
+                CollectionDockingView.Instance.StartOtherDrag([docFunc()], e);
+            } else {
+                document.addEventListener("pointermove", onRowMove);
+                document.addEventListener("pointerup", onRowUp);
+            }
+        }
+        //}
+    };
+    return onItemDown;
 }
 
 export namespace DragManager {
-  export function Root() {
-    const root = document.getElementById("root");
-    if (!root) {
-      throw new Error("No root element found");
+    export function Root() {
+        const root = document.getElementById("root");
+        if (!root) {
+            throw new Error("No root element found");
+        }
+        return root;
     }
-    return root;
-  }
 
-  let dragDiv: HTMLDivElement;
+    let dragDiv: HTMLDivElement;
 
-  export enum DragButtons {
-    Left = 1,
-    Right = 2,
-    Both = Left | Right
-  }
+    export enum DragButtons {
+        Left = 1,
+        Right = 2,
+        Both = Left | Right
+    }
 
-  interface DragOptions {
-    handlers: DragHandlers;
+    interface DragOptions {
+        handlers: DragHandlers;
 
-    hideSource: boolean | (() => boolean);
-  }
+        hideSource: boolean | (() => boolean);
+    }
 
-  export interface DragDropDisposer {
-    (): void;
-  }
+    export interface DragDropDisposer {
+        (): void;
+    }
 
-  export class DragCompleteEvent {}
+    export class DragCompleteEvent { }
 
-  export interface DragHandlers {
-    dragComplete: (e: DragCompleteEvent) => void;
-  }
+    export interface DragHandlers {
+        dragComplete: (e: DragCompleteEvent) => void;
+    }
 
-  export interface DropOptions {
-    handlers: DropHandlers;
-  }
-  export class DropEvent {
-    constructor(
-      readonly x: number,
-      readonly y: number,
-      readonly data: { [id: string]: any }
-    ) {}
-  }
+    export interface DropOptions {
+        handlers: DropHandlers;
+    }
+    export class DropEvent {
+        constructor(
+            readonly x: number,
+            readonly y: number,
+            readonly data: { [id: string]: any }
+        ) { }
+    }
 
-  export interface DropHandlers {
-    drop: (e: Event, de: DropEvent) => void;
-  }
+    export interface DropHandlers {
+        drop: (e: Event, de: DropEvent) => void;
+    }
 
-  export function MakeDropTarget(
-    element: HTMLElement,
-    options: DropOptions
-  ): DragDropDisposer {
-    if ("canDrop" in element.dataset) {
-      throw new Error(
-        "Element is already droppable, can't make it droppable again"
-      );
+    export function MakeDropTarget(
+        element: HTMLElement,
+        options: DropOptions
+    ): DragDropDisposer {
+        if ("canDrop" in element.dataset) {
+            throw new Error(
+                "Element is already droppable, can't make it droppable again"
+            );
+        }
+        element.dataset["canDrop"] = "true";
+        const handler = (e: Event) => {
+            const ce = e as CustomEvent<DropEvent>;
+            options.handlers.drop(e, ce.detail);
+        };
+        element.addEventListener("dashOnDrop", handler);
+        return () => {
+            element.removeEventListener("dashOnDrop", handler);
+            delete element.dataset["canDrop"];
+        };
     }
-    element.dataset["canDrop"] = "true";
-    const handler = (e: Event) => {
-      const ce = e as CustomEvent<DropEvent>;
-      options.handlers.drop(e, ce.detail);
-    };
-    element.addEventListener("dashOnDrop", handler);
-    return () => {
-      element.removeEventListener("dashOnDrop", handler);
-      delete element.dataset["canDrop"];
-    };
-  }
 
-  export class DocumentDragData {
-    constructor(dragDoc: Document[]) {
-      this.draggedDocuments = dragDoc;
-      this.droppedDocuments = dragDoc;
+    export class DocumentDragData {
+        constructor(dragDoc: Document[]) {
+            this.draggedDocuments = dragDoc;
+            this.droppedDocuments = dragDoc;
+        }
+        draggedDocuments: Document[];
+        droppedDocuments: Document[];
+        xOffset?: number;
+        yOffset?: number;
+        aliasOnDrop?: boolean;
+        removeDocument?: (collectionDrop: CollectionView) => void;
+        [id: string]: any;
     }
-    draggedDocuments: Document[];
-    droppedDocuments: Document[];
-    xOffset?: number;
-    yOffset?: number;
-    aliasOnDrop?: boolean;
-    removeDocument?: (collectionDrop: CollectionView) => void;
-    [id: string]: any;
-  }
 
-  export function StartDocumentDrag(
-    eles: HTMLElement[],
-    dragData: DocumentDragData,
-    options?: DragOptions
-  ) {
-    StartDrag(
-      eles,
-      dragData,
-      options,
-      (dropData: { [id: string]: any }) =>
-        (dropData.droppedDocuments = dragData.aliasOnDrop
-          ? dragData.draggedDocuments.map(d => d.CreateAlias())
-          : dragData.draggedDocuments)
-    );
-  }
+    export function StartDocumentDrag(
+        eles: HTMLElement[],
+        dragData: DocumentDragData,
+        options?: DragOptions
+    ) {
+        StartDrag(
+            eles,
+            dragData,
+            options,
+            (dropData: { [id: string]: any }) =>
+                (dropData.droppedDocuments = dragData.aliasOnDrop
+                    ? dragData.draggedDocuments.map(d => d.CreateAlias())
+                    : dragData.draggedDocuments)
+        );
+    }
 
-  export class LinkDragData {
-    constructor(linkSourceDoc: DocumentView) {
-      this.linkSourceDocumentView = linkSourceDoc;
+    export class LinkDragData {
+        constructor(linkSourceDoc: DocumentView) {
+            this.linkSourceDocumentView = linkSourceDoc;
+        }
+        droppedDocuments: Document[] = [];
+        linkSourceDocumentView: DocumentView;
+        [id: string]: any;
     }
-    linkSourceDocumentView: DocumentView;
-    [id: string]: any;
-  }
-  export function StartLinkDrag(
-    ele: HTMLElement,
-    dragData: LinkDragData,
-    options?: DragOptions
-  ) {
-    StartDrag([ele], dragData, options);
-  }
-  function StartDrag(
-    eles: HTMLElement[],
-    dragData: { [id: string]: any },
-    options?: DragOptions,
-    finishDrag?: (dropData: { [id: string]: any }) => void
-  ) {
-    if (!dragDiv) {
-      dragDiv = document.createElement("div");
-      dragDiv.className = "dragManager-dragDiv";
-      DragManager.Root().appendChild(dragDiv);
+    export function StartLinkDrag(
+        ele: HTMLElement,
+        dragData: LinkDragData,
+        options?: DragOptions
+    ) {
+        StartDrag([ele], dragData, options);
     }
+    function StartDrag(
+        eles: HTMLElement[],
+        dragData: { [id: string]: any },
+        options?: DragOptions,
+        finishDrag?: (dropData: { [id: string]: any }) => void
+    ) {
+        if (!dragDiv) {
+            dragDiv = document.createElement("div");
+            dragDiv.className = "dragManager-dragDiv";
+            DragManager.Root().appendChild(dragDiv);
+        }
 
-    let scaleXs: number[] = [];
-    let scaleYs: number[] = [];
-    let xs: number[] = [];
-    let ys: number[] = [];
+        let scaleXs: number[] = [];
+        let scaleYs: number[] = [];
+        let xs: number[] = [];
+        let ys: number[] = [];
 
-    const docs: Document[] =
-      dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
-    let dragElements = eles.map(ele => {
-      const w = ele.offsetWidth,
-        h = ele.offsetHeight;
-      const rect = ele.getBoundingClientRect();
-      const scaleX = rect.width / w,
-        scaleY = rect.height / h;
-      let x = rect.left,
-        y = rect.top;
-      xs.push(x);
-      ys.push(y);
-      scaleXs.push(scaleX);
-      scaleYs.push(scaleY);
-      let dragElement = ele.cloneNode(true) as HTMLElement;
-      dragElement.style.opacity = "0.7";
-      dragElement.style.position = "absolute";
-      dragElement.style.bottom = "";
-      dragElement.style.left = "";
-      dragElement.style.transformOrigin = "0 0";
-      dragElement.style.zIndex = "1000";
-      dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
-      dragElement.style.width = `${rect.width / scaleX}px`;
-      dragElement.style.height = `${rect.height / scaleY}px`;
+        const docs: Document[] =
+            dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
+        let dragElements = eles.map(ele => {
+            const w = ele.offsetWidth,
+                h = ele.offsetHeight;
+            const rect = ele.getBoundingClientRect();
+            const scaleX = rect.width / w,
+                scaleY = rect.height / h;
+            let x = rect.left,
+                y = rect.top;
+            xs.push(x);
+            ys.push(y);
+            scaleXs.push(scaleX);
+            scaleYs.push(scaleY);
+            let dragElement = ele.cloneNode(true) as HTMLElement;
+            dragElement.style.opacity = "0.7";
+            dragElement.style.position = "absolute";
+            dragElement.style.margin = "0";
+            dragElement.style.top = "0";
+            dragElement.style.bottom = "";
+            dragElement.style.left = "0";
+            dragElement.style.transformOrigin = "0 0";
+            dragElement.style.zIndex = "1000";
+            dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+            dragElement.style.width = `${rect.width / scaleX}px`;
+            dragElement.style.height = `${rect.height / scaleY}px`;
 
-      // bcz: PDFs don't show up if you clone them because they contain a canvas.
-      //      however, PDF's have a thumbnail field that contains an image of their canvas.
-      //      So we replace the pdf's canvas with the image thumbnail
-      if (docs.length) {
-        var pdfBox = dragElement.getElementsByClassName(
-          "pdfBox-cont"
-        )[0] as HTMLElement;
-        let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField);
-        if (pdfBox && pdfBox.childElementCount && thumbnail) {
-          let img = new Image();
-          img!.src = thumbnail.toString();
-          img!.style.position = "absolute";
-          img!.style.width = `${rect.width / scaleX}px`;
-          img!.style.height = `${rect.height / scaleY}px`;
-          pdfBox.replaceChild(img!, pdfBox.children[0]);
+            // bcz: PDFs don't show up if you clone them because they contain a canvas.
+            //      however, PDF's have a thumbnail field that contains an image of their canvas.
+            //      So we replace the pdf's canvas with the image thumbnail
+            if (docs.length) {
+                var pdfBox = dragElement.getElementsByClassName(
+                    "pdfBox-cont"
+                )[0] as HTMLElement;
+                let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField);
+                if (pdfBox && pdfBox.childElementCount && thumbnail) {
+                    let img = new Image();
+                    img!.src = thumbnail.toString();
+                    img!.style.position = "absolute";
+                    img!.style.width = `${rect.width / scaleX}px`;
+                    img!.style.height = `${rect.height / scaleY}px`;
+                    pdfBox.replaceChild(img!, pdfBox.children[0]);
+                }
+            }
+
+            dragDiv.appendChild(dragElement);
+            return dragElement;
+        });
+
+        let hideSource = false;
+        if (options) {
+            if (typeof options.hideSource === "boolean") {
+                hideSource = options.hideSource;
+            } else {
+                hideSource = options.hideSource();
+            }
         }
-      }
+        eles.map(ele => (ele.hidden = hideSource));
 
-      dragDiv.appendChild(dragElement);
-      return dragElement;
-    });
+        const moveHandler = (e: PointerEvent) => {
+            e.stopPropagation();
+            e.preventDefault();
+            if (dragData instanceof DocumentDragData)
+                dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+            if (e.shiftKey) {
+                abortDrag();
+                CollectionDockingView.Instance.StartOtherDrag(docs, {
+                    pageX: e.pageX,
+                    pageY: e.pageY,
+                    preventDefault: () => { },
+                    button: 0
+                });
+            }
+            dragElements.map(
+                (dragElement, i) =>
+                    (dragElement.style.transform = `translate(${(xs[i] +=
+                        e.movementX)}px, ${(ys[i] += e.movementY)}px) scale(${
+                        scaleXs[i]
+                        }, ${scaleYs[i]})`)
+            );
+        };
 
-    let hideSource = false;
-    if (options) {
-      if (typeof options.hideSource === "boolean") {
-        hideSource = options.hideSource;
-      } else {
-        hideSource = options.hideSource();
-      }
+        const abortDrag = () => {
+            document.removeEventListener("pointermove", moveHandler, true);
+            document.removeEventListener("pointerup", upHandler);
+            dragElements.map(dragElement => dragDiv.removeChild(dragElement));
+            eles.map(ele => (ele.hidden = false));
+        };
+        const upHandler = (e: PointerEvent) => {
+            abortDrag();
+            FinishDrag(eles, e, dragData, options, finishDrag);
+        };
+        document.addEventListener("pointermove", moveHandler, true);
+        document.addEventListener("pointerup", upHandler);
     }
-    eles.map(ele => (ele.hidden = hideSource));
 
-    const moveHandler = (e: PointerEvent) => {
-      e.stopPropagation();
-      e.preventDefault();
-      if (dragData instanceof DocumentDragData)
-        dragData.aliasOnDrop = e.ctrlKey || e.altKey;
-      if (e.shiftKey) {
-        abortDrag();
-        CollectionDockingView.Instance.StartOtherDrag(docs, {
-          pageX: e.pageX,
-          pageY: e.pageY,
-          preventDefault: () => {},
-          button: 0
+    function FinishDrag(
+        dragEles: HTMLElement[],
+        e: PointerEvent,
+        dragData: { [index: string]: any },
+        options?: DragOptions,
+        finishDrag?: (dragData: { [index: string]: any }) => void
+    ) {
+        let removed = dragEles.map(dragEle => {
+            let parent = dragEle.parentElement;
+            if (parent) parent.removeChild(dragEle);
+            return [dragEle, parent];
         });
-      }
-      dragElements.map(
-        (dragElement, i) =>
-          (dragElement.style.transform = `translate(${(xs[i] +=
-            e.movementX)}px, ${(ys[i] += e.movementY)}px) scale(${
-            scaleXs[i]
-          }, ${scaleYs[i]})`)
-      );
-    };
-
-    const abortDrag = () => {
-      document.removeEventListener("pointermove", moveHandler, true);
-      document.removeEventListener("pointerup", upHandler);
-      dragElements.map(dragElement => dragDiv.removeChild(dragElement));
-      eles.map(ele => (ele.hidden = false));
-    };
-    const upHandler = (e: PointerEvent) => {
-      abortDrag();
-      FinishDrag(eles, e, dragData, options, finishDrag);
-    };
-    document.addEventListener("pointermove", moveHandler, true);
-    document.addEventListener("pointerup", upHandler);
-  }
-
-  function FinishDrag(
-    dragEles: HTMLElement[],
-    e: PointerEvent,
-    dragData: { [index: string]: any },
-    options?: DragOptions,
-    finishDrag?: (dragData: { [index: string]: any }) => void
-  ) {
-    let removed = dragEles.map(dragEle => {
-      let parent = dragEle.parentElement;
-      if (parent) parent.removeChild(dragEle);
-      return [dragEle, parent];
-    });
-    const target = document.elementFromPoint(e.x, e.y);
-    removed.map(r => {
-      let dragEle: HTMLElement = r[0]!;
-      let parent: HTMLElement | null = r[1];
-      if (parent) parent.appendChild(dragEle);
-    });
-    if (target) {
-      if (finishDrag) finishDrag(dragData);
+        const target = document.elementFromPoint(e.x, e.y);
+        removed.map(r => {
+            let dragEle: HTMLElement = r[0]!;
+            let parent: HTMLElement | null = r[1];
+            if (parent) parent.appendChild(dragEle);
+        });
+        if (target) {
+            if (finishDrag) finishDrag(dragData);
 
-      target.dispatchEvent(
-        new CustomEvent<DropEvent>("dashOnDrop", {
-          bubbles: true,
-          detail: {
-            x: e.x,
-            y: e.y,
-            data: dragData
-          }
-        })
-      );
+            target.dispatchEvent(
+                new CustomEvent<DropEvent>("dashOnDrop", {
+                    bubbles: true,
+                    detail: {
+                        x: e.x,
+                        y: e.y,
+                        data: dragData
+                    }
+                })
+            );
 
-      if (options) {
-        options.handlers.dragComplete({});
-      }
+            if (options) {
+                options.handlers.dragComplete({});
+            }
+        }
+        DocumentDecorations.Instance.Hidden = false;
     }
-    DocumentDecorations.Instance.Hidden = false;
-  }
 }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 438659108..958c14491 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,48 +3,46 @@ import { DocumentView } from "../views/nodes/DocumentView";
 import { Document } from "../../fields/Document";
 
 export namespace SelectionManager {
-  class Manager {
-    @observable
-    SelectedDocuments: Array<DocumentView> = [];
-
-    @action
-    SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
-      // if doc is not in SelectedDocuments, add it
-      if (!ctrlPressed) {
-        manager.SelectedDocuments = [];
-      }
-
-      if (manager.SelectedDocuments.indexOf(doc) === -1) {
-        manager.SelectedDocuments.push(doc);
-      }
+    class Manager {
+        @observable
+        SelectedDocuments: Array<DocumentView> = [];
+
+        @action
+        SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+            // if doc is not in SelectedDocuments, add it
+            if (!ctrlPressed) {
+                manager.SelectedDocuments = [];
+            }
+
+            if (manager.SelectedDocuments.indexOf(doc) === -1) {
+                manager.SelectedDocuments.push(doc);
+            }
+        }
     }
-  }
 
-  const manager = new Manager();
+    const manager = new Manager();
 
-  export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
-    if (!doc.isMinimized()) {
-      manager.SelectDoc(doc, ctrlPressed);
+    export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+        manager.SelectDoc(doc, ctrlPressed);
     }
-  }
-
-  export function IsSelected(doc: DocumentView): boolean {
-    return manager.SelectedDocuments.indexOf(doc) !== -1;
-  }
-
-  export function DeselectAll(except?: Document): void {
-    let found: DocumentView | undefined = undefined;
-    if (except) {
-      for (let i = 0; i < manager.SelectedDocuments.length; i++) {
-        let view = manager.SelectedDocuments[i];
-        if (view.props.Document == except) found = view;
-      }
+
+    export function IsSelected(doc: DocumentView): boolean {
+        return manager.SelectedDocuments.indexOf(doc) !== -1;
+    }
+
+    export function DeselectAll(except?: Document): void {
+        let found: DocumentView | undefined = undefined;
+        if (except) {
+            for (let i = 0; i < manager.SelectedDocuments.length; i++) {
+                let view = manager.SelectedDocuments[i];
+                if (view.props.Document == except) found = view;
+            }
+        }
+        manager.SelectedDocuments.length = 0;
+        if (found) manager.SelectedDocuments.push(found);
     }
-    manager.SelectedDocuments.length = 0;
-    if (found) manager.SelectedDocuments.push(found);
-  }
 
-  export function SelectedDocuments(): Array<DocumentView> {
-    return manager.SelectedDocuments;
-  }
+    export function SelectedDocuments(): Array<DocumentView> {
+        return manager.SelectedDocuments;
+    }
 }
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index befe175b5..080c9fa1e 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -2,13 +2,16 @@
 
 #documentDecorations-container {
     position: absolute;
+    top: 0;
+    left:0;
     display: grid;
     z-index: 1000;
     grid-template-rows: 20px 8px 1fr 8px;
-    grid-template-columns: 8px 1fr 8px;
+    grid-template-columns: 8px 8px 1fr 8px;
     pointer-events: none;
 
     #documentDecorations-centerCont {
+        grid-column:3;
         background: none;
     }
 
@@ -18,6 +21,24 @@
         opacity: 0.8;
     }
 
+    #documentDecorations-topLeftResizer,
+    #documentDecorations-leftResizer,
+    #documentDecorations-bottomLeftResizer {
+        grid-column: 1
+    }
+
+    #documentDecorations-topResizer,
+    #documentDecorations-bottomResizer {
+        grid-column-start: 2;
+        grid-column-end: 4;
+    }
+
+    #documentDecorations-bottomRightResizer,
+    #documentDecorations-topRightResizer,
+    #documentDecorations-rightResizer {
+        grid-column:4
+    }
+
     #documentDecorations-topLeftResizer,
     #documentDecorations-bottomRightResizer {
         cursor: nwse-resize;
@@ -39,15 +60,19 @@
     }
     .title{
         background: lightblue;
-        grid-column-start:2;
-        grid-column-end: 4;
+        grid-column-start:3;
+        grid-column-end: 5;
         pointer-events: auto;
     }
 }
 
 .documentDecorations-minimizeButton {
-    background: rgb(250, 57, 9);
+    background:$alt-accent;
+    opacity: 0.8;
+    grid-column-start: 1;
+    grid-column-end: 3;
     pointer-events: all;
+    text-align: center;
 }
 .documentDecorations-background {
     background: lightblue;
@@ -87,7 +112,8 @@
 //     }
 // }
 .linkFlyout {
-    grid-column: 1/4
+    grid-column: 1/4;
+    margin-left: 25px;
 }
 
 .linkButton-empty:hover {
@@ -102,6 +128,31 @@
     cursor: pointer;
 }
 
+.linkButton-linker {
+    position:absolute;
+    bottom:0px;
+    left: 0px;
+    height: 20px;
+    width: 20px;
+    margin-top: 10px;
+    margin-right: 5px;
+    border-radius: 50%;
+    opacity: 0.9;
+    pointer-events: auto;
+    color: $dark-color;
+    border: $dark-color 1px solid;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    font-size: 75%;
+    transition: transform 0.2s;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.linkButton-tray {
+    grid-column: 1/4;
+}
 .linkButton-empty {
     height: 20px;
     width: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b74737ec9..a46087c9a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,10 +1,11 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Key } from "../../fields/Key";
 //import ContentEditable from 'react-contenteditable'
 import { KeyStore } from "../../fields/KeyStore";
 import { ListField } from "../../fields/ListField";
 import { NumberField } from "../../fields/NumberField";
+import { Document } from "../../fields/Document";
 import { TextField } from "../../fields/TextField";
 import { DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
@@ -13,6 +14,7 @@ import './DocumentDecorations.scss';
 import { DocumentView } from "./nodes/DocumentView";
 import { LinkMenu } from "./nodes/LinkMenu";
 import React = require("react");
+import { FieldWaiting } from "../../fields/Field";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -28,6 +30,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     private _linkBoxHeight = 30;
     private _titleHeight = 20;
     private _linkButton = React.createRef<HTMLDivElement>();
+    private _linkerButton = React.createRef<HTMLDivElement>();
     //@observable private _title: string = this._documents[0].props.Document.Title;
     @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : "";
     @observable private _fieldKey: Key = KeyStore.Title;
@@ -174,18 +177,40 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
     }
 
-    onLinkButtonDown = (e: React.PointerEvent): void => {
-        // if ()
-        // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]);
-        // linkMenu.Hidden = false;
-        console.log("down");
+    onLinkerButtonDown = (e: React.PointerEvent): void => {
+        e.stopPropagation();
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+        document.addEventListener("pointermove", this.onLinkerButtonMoved);
+        document.removeEventListener("pointerup", this.onLinkerButtonUp)
+        document.addEventListener("pointerup", this.onLinkerButtonUp);
+    }
+    onLinkerButtonUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+        document.removeEventListener("pointerup", this.onLinkerButtonUp)
+        e.stopPropagation();
+    }
 
+    onLinkerButtonMoved = (e: PointerEvent): void => {
+        if (this._linkerButton.current != null) {
+            document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+            document.removeEventListener("pointerup", this.onLinkerButtonUp)
+            let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
+            DragManager.StartLinkDrag(this._linkerButton.current, dragData, {
+                handlers: {
+                    dragComplete: action(() => { }),
+                },
+                hideSource: false
+            })
+        }
+        e.stopPropagation();
+    }
+
+    onLinkButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
         document.removeEventListener("pointermove", this.onLinkButtonMoved)
         document.addEventListener("pointermove", this.onLinkButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp)
         document.addEventListener("pointerup", this.onLinkButtonUp);
-
     }
 
     onLinkButtonUp = (e: PointerEvent): void => {
@@ -194,18 +219,32 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         e.stopPropagation();
     }
 
-
     onLinkButtonMoved = (e: PointerEvent): void => {
         if (this._linkButton.current != null) {
             document.removeEventListener("pointermove", this.onLinkButtonMoved)
-            document.removeEventListener("pointerup", this.onLinkButtonUp)
-            let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
-            DragManager.StartLinkDrag(this._linkButton.current, dragData, {
-                handlers: {
-                    dragComplete: action(() => { }),
-                },
-                hideSource: false
-            })
+            document.removeEventListener("pointerup", this.onLinkButtonUp);
+
+            let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+            let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+            let draggedDocs = (srcTarg && srcTarg != FieldWaiting) ?
+                srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
+                    (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
+            let draggedFromDocs = (srcTarg && srcTarg != FieldWaiting) ?
+                srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
+                    (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
+            draggedDocs.push(...draggedFromDocs);
+            if (draggedDocs.length) {
+                let dragData = new DragManager.DocumentDragData(draggedDocs.map(d => {
+                    let annot = d.GetT(KeyStore.AnnotationOn, Document); // bcz: ... needs to change ... the annotationOn document may not have been retrieved yet...
+                    return annot && annot != FieldWaiting ? annot : d;
+                }));
+                DragManager.StartDocumentDrag([this._linkButton.current], dragData, {
+                    handlers: {
+                        dragComplete: action(() => { }),
+                    },
+                    hideSource: false
+                })
+            }
         }
         e.stopPropagation();
     }
@@ -298,7 +337,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     getValue = (): string => {
-        console.log("value watched");
         if (this._title === "changed" && this._documents.length > 0) {
             let field = this._documents[0].props.Document.Get(this._fieldKey);
             if (field instanceof TextField) {
@@ -338,8 +376,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             linkButton = (<Flyout
                 anchorPoint={anchorPoints.RIGHT_TOP}
                 content={
-                    <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
-                    </LinkMenu>
+                    <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent} />
                 }>
                 <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
             </Flyout>);
@@ -361,7 +398,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
                 opacity: this._opacity
             }}>
-                <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>v</div>
+                <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
                 <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onPointerDown} onKeyPress={this.enterPressed} />
                 <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
@@ -373,8 +410,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
 
-                <div title="View Links" className="linkFlyout" ref={this._linkButton}>{linkButton}</div>
-
+                <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div>
+                <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
             </div >
         </div>
         )
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index 35c8ee942..c5a2a50cb 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -2,6 +2,8 @@
 
 .inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect {
     position: absolute;
+    top: 0;
+    left:0;
     width: 8192px;
     height: 8192px;
     cursor:"crosshair";
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 698a9c617..594803aab 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -7,6 +7,9 @@ body {
     overflow: hidden;
     font-family: $sans-serif;
     margin: 0;
+    position:absolute;
+    top: 0;
+    left:0;
 }
 
 #dash-title {
@@ -158,11 +161,15 @@ button:hover {
     width:100%;
     height:100%;
     position:absolute;
+    top: 0;
+    left:0;
 }
 #mainContent-div {
     width:100%;
     height:100%;
     position:absolute;
+    top: 0;
+    left:0;
 }
 #add-options-content {
     display: table;
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 2706c3272..583d50c5b 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -3,7 +3,7 @@
 }
 
 .collectiondockingview-container {
-    position: relative;
+    position: absolute;
     top: 0;
     left: 0;
     overflow: hidden;
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index 0144625c1..0eca3f1cd 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -9,6 +9,8 @@
     width: 100%;
     height: 100%;
     position: absolute; 
+    top: 0;
+    left:0;
     
 }
 .collectionPdfView-backward {
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
index cbb981b13..ed56ad268 100644
--- a/src/client/views/collections/CollectionVideoView.scss
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -3,6 +3,8 @@
     width: 100%;
     height: 100%;
     position: absolute;
+    top: 0;
+    left:0;
  
 }
 .collectionVideoView-time{
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 2b0689800..daeca69e2 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -17,6 +17,8 @@ import { NumberField } from "../../../fields/NumberField";
 import request = require("request");
 import { ServerUtils } from "../../../server/ServerUtil";
 import { Server } from "../../Server";
+import { CollectionDockingView } from "./CollectionDockingView";
+import { runReactions } from "mobx/lib/internal";
 
 export interface CollectionViewProps {
     fieldKey: Key;
@@ -92,6 +94,24 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
             e.stopPropagation();
             return added;
         }
+        if (de.data instanceof DragManager.LinkDragData) {
+            let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
+            if (sourceDoc) runInAction(() => {
+                let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document)
+                if (srcTarg && srcTarg != FieldWaiting) {
+                    let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]);
+                    linkDocs.map(linkDoc => {
+                        let targDoc = linkDoc.GetT(KeyStore.LinkedToDocs, Document);
+                        if (targDoc && targDoc != FieldWaiting) {
+                            let dropdoc = targDoc.MakeDelegate();
+                            de.data.droppedDocuments.push(dropdoc);
+                            this.props.addDocument(dropdoc, false);
+                        }
+                    })
+                }
+            })
+            return true;
+        }
         return false;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index e84f0c5ad..3dfd74ec8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -23,10 +23,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         let l = this.props.LinkDocs;
         let a = this.props.A;
         let b = this.props.B;
-        let x1 = a.GetNumber(KeyStore.X, 0) + a.GetNumber(KeyStore.Width, 0) / 2;
-        let y1 = a.GetNumber(KeyStore.Y, 0) + a.GetNumber(KeyStore.Height, 0) / 2;
-        let x2 = b.GetNumber(KeyStore.X, 0) + b.GetNumber(KeyStore.Width, 0) / 2;
-        let y2 = b.GetNumber(KeyStore.Y, 0) + b.GetNumber(KeyStore.Height, 0) / 2;
+        let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Width, 0) / 2);
+        let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Height, 0) / 2);
+        let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Width, 0) / 2);
+        let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Height, 0) / 2);
         return (
             <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
                 style={{ strokeWidth: `${l.length * 5}` }}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
index 4341c82f7..30e158603 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -1,6 +1,8 @@
 .collectionfreeformlinksview-svgCanvas{
     transform: translate(-10000px,-10000px);
     position: absolute;
+    top: 0;
+    left: 0;
     width: 20000px; 
     height: 20000px;
     pointer-events: none;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 81d21d89a..79d520069 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -26,7 +26,7 @@
   border: 0px solid $light-color-secondary;
   border-radius: $border-radius;
   box-sizing: border-box;
-  position: relative;
+  position: absolute;
   overflow: hidden;
   top: 0;
   left: 0;
@@ -41,12 +41,12 @@
   .formattedTextBox-cont {
     background: $light-color-secondary;
   }
-
+ 
   opacity: 0.99;
   border: 0px solid transparent;
   border-radius: $border-radius;
   box-sizing: border-box;
-  position:relative;
+  position:absolute;
   overflow: hidden;
   top: 0;
   left: 0;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c5178f69d..3484e963e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -73,14 +73,17 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (super.drop(e, de)) {
-            if (de.data instanceof DragManager.DocumentDragData) {
-                let screenX = de.x - (de.data.xOffset as number || 0);
-                let screenY = de.y - (de.data.yOffset as number || 0);
+            let droppedDocs = de.data.droppedDocuments as Document[];
+            let xoff = de.data.xOffset as number || 0;
+            let yoff = de.data.yOffset as number || 0;
+            if (droppedDocs && droppedDocs.length) {
+                let screenX = de.x - xoff;
+                let screenY = de.y - yoff;
                 const [x, y] = this.getTransform().transformPoint(screenX, screenY);
-                let dragDoc = de.data.draggedDocuments[0];
+                let dragDoc = de.data.droppedDocuments[0];
                 let dragX = dragDoc.GetNumber(KeyStore.X, 0);
                 let dragY = dragDoc.GetNumber(KeyStore.Y, 0);
-                de.data.draggedDocuments.map(d => {
+                droppedDocs.map(d => {
                     let docX = d.GetNumber(KeyStore.X, 0);
                     let docY = d.GetNumber(KeyStore.Y, 0);
                     d.SetNumber(KeyStore.X, x + (docX - dragX));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 1ee3b244b..0b406e722 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -1,6 +1,8 @@
 
 .marqueeView {
     position: absolute;
+    top:0;
+    left:0;
     width:100%;
     height:100%;
 }
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
index 21210be2b..7a67c29bf 100644
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
@@ -3,9 +3,13 @@
     color: black;
     position: absolute;
     transform-origin: left top;
+    top: 0;
+    left:0;
     pointer-events: none;
 }
 .previewCursorView {
+    top: 0;
+    left:0;
     position: absolute;
     width:100%;
     height:100%;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d52b662bd..1a0f0cbbd 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -66,8 +66,12 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
         return <DocumentView {...this.props}
             ContentScaling={this.contentScaling}
             ScreenToLocalTransform={this.getTransform}
+            PanelWidth={this.panelWidth}
+            PanelHeight={this.panelHeight}
         />
     }
+    panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
+    panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
 
     render() {
         return (
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 127a6b535..5126e69f9 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -2,6 +2,8 @@
 
 .documentView-node {
   position: absolute;
+  top: 0;
+  left:0;
   background: $light-color; //overflow: hidden;
 
   &.minimized {
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index ad947afd5..830dfe6c6 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,12 +1,16 @@
 .react-pdf__Page {
     transform-origin: left top;
     position: absolute;
+    top: 0;
+    left:0;
 }
 .react-pdf__Document {
     position: absolute;
 }
 .pdfBox-buttonTray {
     position:absolute;
+    top: 0;
+    left:0;
     z-index: 25;
 }
 .pdfBox-contentContainer {
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index a535b2638..c73bc0c47 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -2,6 +2,8 @@
 .webBox-cont {
     padding: 0vw;
     position: absolute;
+    top: 0;
+    left:0;
     width: 100%;
     height: 100%;
     overflow: scroll;
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 8e71019a4..92d03d765 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -9,415 +9,420 @@ import { Server } from "../client/Server";
 import { Types } from "../server/Message";
 import { UndoManager } from "../client/util/UndoManager";
 import { HtmlField } from "./HtmlField";
+import { BooleanField } from "./BooleanField";
 
 export class Document extends Field {
-  //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
-  public fields: ObservableMap<
-    string,
-    { key: Key; field: Field }
-  > = new ObservableMap();
-  public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
-
-  constructor(id?: string, save: boolean = true) {
-    super(id);
-
-    if (save) {
-      Server.UpdateField(this);
+    //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
+    public fields: ObservableMap<
+        string,
+        { key: Key; field: Field }
+    > = new ObservableMap();
+    public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
+
+    constructor(id?: string, save: boolean = true) {
+        super(id);
+
+        if (save) {
+            Server.UpdateField(this);
+        }
     }
-  }
 
-  UpdateFromServer(data: [string, string][]) {
-    for (const key in data) {
-      const element = data[key];
-      this._proxies.set(element[0], element[1]);
-    }
-  }
-
-  public Width = () => {
-    return this.GetNumber(KeyStore.Width, 0);
-  };
-  public Height = () => {
-    return this.GetNumber(
-      KeyStore.Height,
-      this.GetNumber(KeyStore.NativeWidth, 0)
-        ? (this.GetNumber(KeyStore.NativeHeight, 0) /
-            this.GetNumber(KeyStore.NativeWidth, 0)) *
-            this.GetNumber(KeyStore.Width, 0)
-        : 0
-    );
-  };
-  public Scale = () => {
-    return this.GetNumber(KeyStore.Scale, 1);
-  };
-
-  @computed
-  public get Title(): string {
-    let title = this.Get(KeyStore.Title, true);
-    if (title)
-      if (title != FieldWaiting && title instanceof TextField)
-        return title.Data;
-      else return "-waiting-";
-    let parTitle = this.GetT(KeyStore.Title, TextField);
-    if (parTitle)
-      if (parTitle != FieldWaiting) return parTitle.Data + ".alias";
-      else return "-waiting-.alias";
-    return "-untitled-";
-  }
-
-  @computed
-  public get Fields() {
-    return this.fields;
-  }
-
-  /**
-   * Get the field in the document associated with the given key. If the
-   * associated field has not yet been filled in from the server, a request
-   * to the server will automatically be sent, the value will be filled in
-   * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned.
-   * @param key - The key of the value to get
-   * @param ignoreProto - If true, ignore any prototype this document
-   * might have and only search for the value on this immediate document.
-   * If false (default), search up the prototype chain, starting at this document,
-   * for a document that has a field associated with the given key, and return the first
-   * one found.
-   *
-   * @returns If the document does not have a field associated with the given key, returns `undefined`.
-   * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}.
-   * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field.
-   */
-  Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> {
-    let field: FieldValue<Field>;
-    if (ignoreProto) {
-      if (this.fields.has(key.Id)) {
-        field = this.fields.get(key.Id)!.field;
-      } else if (this._proxies.has(key.Id)) {
-        Server.GetDocumentField(this, key);
-        /*
-                The field might have been instantly filled from the cache
-                Maybe we want to just switch back to returning the value
-                from Server.GetDocumentField if it's in the cache
-                */
-        if (this.fields.has(key.Id)) {
-          field = this.fields.get(key.Id)!.field;
-        } else {
-          field = FieldWaiting;
+    UpdateFromServer(data: [string, string][]) {
+        for (const key in data) {
+            const element = data[key];
+            this._proxies.set(element[0], element[1]);
         }
-      }
-    } else {
-      let doc: FieldValue<Document> = this;
-      while (doc && doc != FieldWaiting && field != FieldWaiting) {
-        let curField = doc.fields.get(key.Id);
-        let curProxy = doc._proxies.get(key.Id);
-        if (!curField || (curProxy && curField.field.Id !== curProxy)) {
-          if (curProxy) {
-            Server.GetDocumentField(doc, key);
-            /*
+    }
+
+    public Width = () => {
+        return this.GetNumber(KeyStore.Width, 0);
+    };
+    public Height = () => {
+        return this.GetNumber(
+            KeyStore.Height,
+            this.GetNumber(KeyStore.NativeWidth, 0)
+                ? (this.GetNumber(KeyStore.NativeHeight, 0) /
+                    this.GetNumber(KeyStore.NativeWidth, 0)) *
+                this.GetNumber(KeyStore.Width, 0)
+                : 0
+        );
+    };
+    public Scale = () => {
+        return this.GetNumber(KeyStore.Scale, 1);
+    };
+
+    @computed
+    public get Title(): string {
+        let title = this.Get(KeyStore.Title, true);
+        if (title)
+            if (title != FieldWaiting && title instanceof TextField)
+                return title.Data;
+            else return "-waiting-";
+        let parTitle = this.GetT(KeyStore.Title, TextField);
+        if (parTitle)
+            if (parTitle != FieldWaiting) return parTitle.Data + ".alias";
+            else return "-waiting-.alias";
+        return "-untitled-";
+    }
+
+    @computed
+    public get Fields() {
+        return this.fields;
+    }
+
+    /**
+     * Get the field in the document associated with the given key. If the
+     * associated field has not yet been filled in from the server, a request
+     * to the server will automatically be sent, the value will be filled in
+     * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned.
+     * @param key - The key of the value to get
+     * @param ignoreProto - If true, ignore any prototype this document
+     * might have and only search for the value on this immediate document.
+     * If false (default), search up the prototype chain, starting at this document,
+     * for a document that has a field associated with the given key, and return the first
+     * one found.
+     *
+     * @returns If the document does not have a field associated with the given key, returns `undefined`.
+     * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}.
+     * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field.
+     */
+    Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> {
+        let field: FieldValue<Field>;
+        if (ignoreProto) {
+            if (this.fields.has(key.Id)) {
+                field = this.fields.get(key.Id)!.field;
+            } else if (this._proxies.has(key.Id)) {
+                Server.GetDocumentField(this, key);
+                /*
                         The field might have been instantly filled from the cache
                         Maybe we want to just switch back to returning the value
                         from Server.GetDocumentField if it's in the cache
                         */
-            if (this.fields.has(key.Id)) {
-              field = this.fields.get(key.Id)!.field;
-            } else {
-              field = FieldWaiting;
+                if (this.fields.has(key.Id)) {
+                    field = this.fields.get(key.Id)!.field;
+                } else {
+                    field = FieldWaiting;
+                }
             }
-            break;
-          }
-          if (
-            doc.fields.has(KeyStore.Prototype.Id) ||
-            doc._proxies.has(KeyStore.Prototype.Id)
-          ) {
-            doc = doc.GetPrototype();
-          } else {
-            break;
-          }
         } else {
-          field = curField.field;
-          break;
+            let doc: FieldValue<Document> = this;
+            while (doc && doc != FieldWaiting && field != FieldWaiting) {
+                let curField = doc.fields.get(key.Id);
+                let curProxy = doc._proxies.get(key.Id);
+                if (!curField || (curProxy && curField.field.Id !== curProxy)) {
+                    if (curProxy) {
+                        Server.GetDocumentField(doc, key);
+                        /*
+                                    The field might have been instantly filled from the cache
+                                    Maybe we want to just switch back to returning the value
+                                    from Server.GetDocumentField if it's in the cache
+                                    */
+                        if (this.fields.has(key.Id)) {
+                            field = this.fields.get(key.Id)!.field;
+                        } else {
+                            field = FieldWaiting;
+                        }
+                        break;
+                    }
+                    if (
+                        doc.fields.has(KeyStore.Prototype.Id) ||
+                        doc._proxies.has(KeyStore.Prototype.Id)
+                    ) {
+                        doc = doc.GetPrototype();
+                    } else {
+                        break;
+                    }
+                } else {
+                    field = curField.field;
+                    break;
+                }
+            }
+            if (doc == FieldWaiting) field = FieldWaiting;
         }
-      }
-      if (doc == FieldWaiting) field = FieldWaiting;
+
+        return field;
     }
 
-    return field;
-  }
-
-  /**
-   * Tries to get the field associated with the given key, and if there is an
-   * associated field, calls the given callback with that field.
-   * @param key - The key of the value to get
-   * @param callback - A function that will be called with the associated field, if it exists,
-   * once it is fetched from the server (this may be immediately if the field has already been fetched).
-   * Note: The callback will not be called if there is no associated field.
-   * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise
-   */
-  GetAsync(key: Key, callback: (field: Opt<Field>) => void): void {
-    //TODO: This currently doesn't deal with prototypes
-    let field = this.fields.get(key.Id);
-    if (field && field.field) {
-      callback(field.field);
-    } else if (this._proxies.has(key.Id)) {
-      Server.GetDocumentField(this, key, callback);
-    } else if (this._proxies.has(KeyStore.Prototype.Id)) {
-      this.GetTAsync(KeyStore.Prototype, Document, proto => {
-        if (proto) {
-          proto.GetAsync(key, callback);
+    /**
+     * Tries to get the field associated with the given key, and if there is an
+     * associated field, calls the given callback with that field.
+     * @param key - The key of the value to get
+     * @param callback - A function that will be called with the associated field, if it exists,
+     * once it is fetched from the server (this may be immediately if the field has already been fetched).
+     * Note: The callback will not be called if there is no associated field.
+     * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise
+     */
+    GetAsync(key: Key, callback: (field: Opt<Field>) => void): void {
+        //TODO: This currently doesn't deal with prototypes
+        let field = this.fields.get(key.Id);
+        if (field && field.field) {
+            callback(field.field);
+        } else if (this._proxies.has(key.Id)) {
+            Server.GetDocumentField(this, key, callback);
+        } else if (this._proxies.has(KeyStore.Prototype.Id)) {
+            this.GetTAsync(KeyStore.Prototype, Document, proto => {
+                if (proto) {
+                    proto.GetAsync(key, callback);
+                } else {
+                    callback(undefined);
+                }
+            });
         } else {
-          callback(undefined);
+            callback(undefined);
         }
-      });
-    } else {
-      callback(undefined);
     }
-  }
-
-  GetTAsync<T extends Field>(key: Key, ctor: { new (): T }): Promise<Opt<T>>;
-  GetTAsync<T extends Field>(
-    key: Key,
-    ctor: { new (): T },
-    callback: (field: Opt<T>) => void
-  ): void;
-  GetTAsync<T extends Field>(
-    key: Key,
-    ctor: { new (): T },
-    callback?: (field: Opt<T>) => void
-  ): Promise<Opt<T>> | void {
-    let fn = (cb: (field: Opt<T>) => void) => {
-      return this.GetAsync(key, field => {
-        cb(Cast(field, ctor));
-      });
-    };
-    if (callback) {
-      fn(callback);
-    } else {
-      return new Promise(res => fn(res));
+
+    GetTAsync<T extends Field>(key: Key, ctor: { new(): T }): Promise<Opt<T>>;
+    GetTAsync<T extends Field>(
+        key: Key,
+        ctor: { new(): T },
+        callback: (field: Opt<T>) => void
+    ): void;
+    GetTAsync<T extends Field>(
+        key: Key,
+        ctor: { new(): T },
+        callback?: (field: Opt<T>) => void
+    ): Promise<Opt<T>> | void {
+        let fn = (cb: (field: Opt<T>) => void) => {
+            return this.GetAsync(key, field => {
+                cb(Cast(field, ctor));
+            });
+        };
+        if (callback) {
+            fn(callback);
+        } else {
+            return new Promise(res => fn(res));
+        }
     }
-  }
-
-  /**
-   * Same as {@link Document#GetAsync}, except a field of the given type
-   * will be created if there is no field associated with the given key,
-   * or the field associated with the given key is not of the given type.
-   * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc.
-   */
-  GetOrCreateAsync<T extends Field>(
-    key: Key,
-    ctor: { new (): T },
-    callback: (field: T) => void
-  ): void {
-    //This currently doesn't deal with prototypes
-    if (this._proxies.has(key.Id)) {
-      Server.GetDocumentField(this, key, field => {
-        if (field && field instanceof ctor) {
-          callback(field);
+
+    /**
+     * Same as {@link Document#GetAsync}, except a field of the given type
+     * will be created if there is no field associated with the given key,
+     * or the field associated with the given key is not of the given type.
+     * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc.
+     */
+    GetOrCreateAsync<T extends Field>(
+        key: Key,
+        ctor: { new(): T },
+        callback: (field: T) => void
+    ): void {
+        //This currently doesn't deal with prototypes
+        if (this._proxies.has(key.Id)) {
+            Server.GetDocumentField(this, key, field => {
+                if (field && field instanceof ctor) {
+                    callback(field);
+                } else {
+                    let newField = new ctor();
+                    this.Set(key, newField);
+                    callback(newField);
+                }
+            });
         } else {
-          let newField = new ctor();
-          this.Set(key, newField);
-          callback(newField);
+            let newField = new ctor();
+            this.Set(key, newField);
+            callback(newField);
         }
-      });
-    } else {
-      let newField = new ctor();
-      this.Set(key, newField);
-      callback(newField);
     }
-  }
-
-  /**
-   * Same as {@link Document#Get}, except that it will additionally
-   * check if the field is of the given type.
-   * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc.
-   * @returns Same as {@link Document#Get}, except will return `undefined`
-   * if there is an associated field but it is of the wrong type.
-   */
-  GetT<T extends Field = Field>(
-    key: Key,
-    ctor: { new (...args: any[]): T },
-    ignoreProto: boolean = false
-  ): FieldValue<T> {
-    var getfield = this.Get(key, ignoreProto);
-    if (getfield != FieldWaiting) {
-      return Cast(getfield, ctor);
+
+    /**
+     * Same as {@link Document#Get}, except that it will additionally
+     * check if the field is of the given type.
+     * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc.
+     * @returns Same as {@link Document#Get}, except will return `undefined`
+     * if there is an associated field but it is of the wrong type.
+     */
+    GetT<T extends Field = Field>(
+        key: Key,
+        ctor: { new(...args: any[]): T },
+        ignoreProto: boolean = false
+    ): FieldValue<T> {
+        var getfield = this.Get(key, ignoreProto);
+        if (getfield != FieldWaiting) {
+            return Cast(getfield, ctor);
+        }
+        return FieldWaiting;
     }
-    return FieldWaiting;
-  }
-
-  GetOrCreate<T extends Field>(
-    key: Key,
-    ctor: { new (): T },
-    ignoreProto: boolean = false
-  ): T {
-    const field = this.GetT(key, ctor, ignoreProto);
-    if (field && field != FieldWaiting) {
-      return field;
+
+    GetOrCreate<T extends Field>(
+        key: Key,
+        ctor: { new(): T },
+        ignoreProto: boolean = false
+    ): T {
+        const field = this.GetT(key, ctor, ignoreProto);
+        if (field && field != FieldWaiting) {
+            return field;
+        }
+        const newField = new ctor();
+        this.Set(key, newField);
+        return newField;
     }
-    const newField = new ctor();
-    this.Set(key, newField);
-    return newField;
-  }
-
-  GetData<T, U extends Field & { Data: T }>(
-    key: Key,
-    ctor: { new (): U },
-    defaultVal: T
-  ): T {
-    let val = this.Get(key);
-    let vval = val && val instanceof ctor ? val.Data : defaultVal;
-    return vval;
-  }
-
-  GetHtml(key: Key, defaultVal: string): string {
-    return this.GetData(key, HtmlField, defaultVal);
-  }
-
-  GetNumber(key: Key, defaultVal: number): number {
-    return this.GetData(key, NumberField, defaultVal);
-  }
-
-  GetText(key: Key, defaultVal: string): string {
-    return this.GetData(key, TextField, defaultVal);
-  }
-
-  GetList<T extends Field>(key: Key, defaultVal: T[]): T[] {
-    return this.GetData<T[], ListField<T>>(key, ListField, defaultVal);
-  }
-
-  @action
-  Set(key: Key, field: Field | undefined, setOnPrototype = false): void {
-    let old = this.fields.get(key.Id);
-    let oldField = old ? old.field : undefined;
-    if (setOnPrototype) {
-      this.SetOnPrototype(key, field);
-    } else {
-      if (field) {
-        this.fields.set(key.Id, { key, field });
-        this._proxies.set(key.Id, field.Id);
-        // Server.AddDocumentField(this, key, field);
-      } else {
-        this.fields.delete(key.Id);
-        this._proxies.delete(key.Id);
-        // Server.DeleteDocumentField(this, key);
-      }
-      Server.UpdateField(this);
+
+    GetData<T, U extends Field & { Data: T }>(
+        key: Key,
+        ctor: { new(): U },
+        defaultVal: T
+    ): T {
+        let val = this.Get(key);
+        let vval = val && val instanceof ctor ? val.Data : defaultVal;
+        return vval;
     }
-    if (oldField || field) {
-      UndoManager.AddEvent({
-        undo: () => this.Set(key, oldField, setOnPrototype),
-        redo: () => this.Set(key, field, setOnPrototype)
-      });
+
+    GetHtml(key: Key, defaultVal: string): string {
+        return this.GetData(key, HtmlField, defaultVal);
     }
-  }
-
-  @action
-  SetOnPrototype(key: Key, field: Field | undefined): void {
-    this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
-      f && f.Set(key, field);
-    });
-  }
-
-  @action
-  SetDataOnPrototype<T, U extends Field & { Data: T }>(
-    key: Key,
-    value: T,
-    ctor: { new (): U },
-    replaceWrongType = true
-  ) {
-    this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
-      f && f.SetData(key, value, ctor);
-    });
-  }
-
-  @action
-  SetData<T, U extends Field & { Data: T }>(
-    key: Key,
-    value: T,
-    ctor: { new (data: T): U },
-    replaceWrongType = true
-  ) {
-    let field = this.Get(key, true);
-    if (field instanceof ctor) {
-      field.Data = value;
-    } else if (!field || replaceWrongType) {
-      let newField = new ctor(value);
-      // newField.Data = value;
-      this.Set(key, newField);
+
+    GetBoolean(key: Key, defaultVal: boolean): boolean {
+        return this.GetData(key, BooleanField, defaultVal);
     }
-  }
-
-  @action
-  SetText(key: Key, value: string, replaceWrongType = true) {
-    this.SetData(key, value, TextField, replaceWrongType);
-  }
-
-  @action
-  SetNumber(key: Key, value: number, replaceWrongType = true) {
-    this.SetData(key, value, NumberField, replaceWrongType);
-  }
-
-  GetPrototype(): FieldValue<Document> {
-    return this.GetT(KeyStore.Prototype, Document, true);
-  }
-
-  GetAllPrototypes(): Document[] {
-    let protos: Document[] = [];
-    let doc: FieldValue<Document> = this;
-    while (doc && doc != FieldWaiting) {
-      protos.push(doc);
-      doc = doc.GetPrototype();
+
+    GetNumber(key: Key, defaultVal: number): number {
+        return this.GetData(key, NumberField, defaultVal);
+    }
+
+    GetText(key: Key, defaultVal: string): string {
+        return this.GetData(key, TextField, defaultVal);
+    }
+
+    GetList<T extends Field>(key: Key, defaultVal: T[]): T[] {
+        return this.GetData<T[], ListField<T>>(key, ListField, defaultVal);
+    }
+
+    @action
+    Set(key: Key, field: Field | undefined, setOnPrototype = false): void {
+        let old = this.fields.get(key.Id);
+        let oldField = old ? old.field : undefined;
+        if (setOnPrototype) {
+            this.SetOnPrototype(key, field);
+        } else {
+            if (field) {
+                this.fields.set(key.Id, { key, field });
+                this._proxies.set(key.Id, field.Id);
+                // Server.AddDocumentField(this, key, field);
+            } else {
+                this.fields.delete(key.Id);
+                this._proxies.delete(key.Id);
+                // Server.DeleteDocumentField(this, key);
+            }
+            Server.UpdateField(this);
+        }
+        if (oldField || field) {
+            UndoManager.AddEvent({
+                undo: () => this.Set(key, oldField, setOnPrototype),
+                redo: () => this.Set(key, field, setOnPrototype)
+            });
+        }
+    }
+
+    @action
+    SetOnPrototype(key: Key, field: Field | undefined): void {
+        this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
+            f && f.Set(key, field);
+        });
+    }
+
+    @action
+    SetDataOnPrototype<T, U extends Field & { Data: T }>(
+        key: Key,
+        value: T,
+        ctor: { new(): U },
+        replaceWrongType = true
+    ) {
+        this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
+            f && f.SetData(key, value, ctor);
+        });
+    }
+
+    @action
+    SetData<T, U extends Field & { Data: T }>(
+        key: Key,
+        value: T,
+        ctor: { new(data: T): U },
+        replaceWrongType = true
+    ) {
+        let field = this.Get(key, true);
+        if (field instanceof ctor) {
+            field.Data = value;
+        } else if (!field || replaceWrongType) {
+            let newField = new ctor(value);
+            // newField.Data = value;
+            this.Set(key, newField);
+        }
+    }
+
+    @action
+    SetText(key: Key, value: string, replaceWrongType = true) {
+        this.SetData(key, value, TextField, replaceWrongType);
+    }
+
+    @action
+    SetNumber(key: Key, value: number, replaceWrongType = true) {
+        this.SetData(key, value, NumberField, replaceWrongType);
+    }
+
+    GetPrototype(): FieldValue<Document> {
+        return this.GetT(KeyStore.Prototype, Document, true);
+    }
+
+    GetAllPrototypes(): Document[] {
+        let protos: Document[] = [];
+        let doc: FieldValue<Document> = this;
+        while (doc && doc != FieldWaiting) {
+            protos.push(doc);
+            doc = doc.GetPrototype();
+        }
+        return protos;
+    }
+
+    CreateAlias(id?: string): Document {
+        let alias = new Document(id);
+        this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
+            f && alias.Set(KeyStore.Prototype, f);
+        });
+
+        return alias;
+    }
+
+    MakeDelegate(id?: string): Document {
+        let delegate = new Document(id);
+
+        delegate.Set(KeyStore.Prototype, this);
+
+        return delegate;
+    }
+
+    ToScriptString(): string {
+        return "";
+    }
+
+    TrySetValue(value: any): boolean {
+        throw new Error("Method not implemented.");
+    }
+    GetValue() {
+        return this.Title;
+        var title =
+            (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) +
+            "(" +
+            this.Id +
+            ")";
+        return title;
+        //throw new Error("Method not implemented.");
+    }
+    Copy(): Field {
+        throw new Error("Method not implemented.");
+    }
+
+    ToJson(): { type: Types; data: [string, string][]; _id: string } {
+        let fields: [string, string][] = [];
+        this._proxies.forEach((field, key) => {
+            if (field) {
+                fields.push([key, field as string]);
+            }
+        });
+
+        return {
+            type: Types.Document,
+            data: fields,
+            _id: this.Id
+        };
     }
-    return protos;
-  }
-
-  CreateAlias(id?: string): Document {
-    let alias = new Document(id);
-    this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
-      f && alias.Set(KeyStore.Prototype, f);
-    });
-
-    return alias;
-  }
-
-  MakeDelegate(id?: string): Document {
-    let delegate = new Document(id);
-
-    delegate.Set(KeyStore.Prototype, this);
-
-    return delegate;
-  }
-
-  ToScriptString(): string {
-    return "";
-  }
-
-  TrySetValue(value: any): boolean {
-    throw new Error("Method not implemented.");
-  }
-  GetValue() {
-    return this.Title;
-    var title =
-      (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) +
-      "(" +
-      this.Id +
-      ")";
-    return title;
-    //throw new Error("Method not implemented.");
-  }
-  Copy(): Field {
-    throw new Error("Method not implemented.");
-  }
-
-  ToJson(): { type: Types; data: [string, string][]; _id: string } {
-    let fields: [string, string][] = [];
-    this._proxies.forEach((field, key) => {
-      if (field) {
-        fields.push([key, field as string]);
-      }
-    });
-
-    return {
-      type: Types.Document,
-      data: fields,
-      _id: this.Id
-    };
-  }
 }
-- 
cgit v1.2.3-70-g09d2


From e70be930dc7c8ff1e999bf163bcbb511a60bae6f Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 4 Apr 2019 17:46:33 -0400
Subject: added close button and fixed some async stuff.

---
 src/client/views/DocumentDecorations.scss          | 17 +++++++---
 src/client/views/DocumentDecorations.tsx           | 38 +++++++++++++++++++---
 .../views/collections/CollectionViewBase.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 +++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 +
 src/client/views/nodes/PDFBox.tsx                  | 10 +++---
 6 files changed, 60 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 080c9fa1e..d7137d7a2 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -7,7 +7,7 @@
     display: grid;
     z-index: 1000;
     grid-template-rows: 20px 8px 1fr 8px;
-    grid-template-columns: 8px 8px 1fr 8px;
+    grid-template-columns: 8px 8px 1fr 8px 8px;
     pointer-events: none;
 
     #documentDecorations-centerCont {
@@ -30,13 +30,14 @@
     #documentDecorations-topResizer,
     #documentDecorations-bottomResizer {
         grid-column-start: 2;
-        grid-column-end: 4;
+        grid-column-end: 5;
     }
 
     #documentDecorations-bottomRightResizer,
     #documentDecorations-topRightResizer,
     #documentDecorations-rightResizer {
-        grid-column:4
+        grid-column-start:5;
+        grid-column-end:7;
     }
 
     #documentDecorations-topLeftResizer,
@@ -61,11 +62,19 @@
     .title{
         background: lightblue;
         grid-column-start:3;
-        grid-column-end: 5;
+        grid-column-end: 4;
         pointer-events: auto;
     }
 }
 
+.documentDecorations-closeButton {
+    background:$alt-accent;
+    opacity: 0.8;
+    grid-column-start: 4;
+    grid-column-end: 6;
+    pointer-events: all;
+    text-align: center;
+}
 .documentDecorations-minimizeButton {
     background:$alt-accent;
     opacity: 0.8;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a46087c9a..8bf1a42d1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -142,6 +142,30 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         e.preventDefault();
     }
 
+    onCloseDown = (e: React.PointerEvent): void => {
+        e.stopPropagation();
+        if (e.button === 0) {
+            document.removeEventListener("pointermove", this.onCloseMove);
+            document.addEventListener("pointermove", this.onCloseMove);
+            document.removeEventListener("pointerup", this.onCloseUp);
+            document.addEventListener("pointerup", this.onCloseUp);
+        }
+    }
+    onCloseMove = (e: PointerEvent): void => {
+        e.stopPropagation();
+        if (e.button === 0) {
+        }
+    }
+    @action
+    onCloseUp = (e: PointerEvent): void => {
+        e.stopPropagation();
+        if (e.button === 0) {
+            SelectionManager.SelectedDocuments().map(dv => dv.props.RemoveDocument && dv.props.RemoveDocument(dv.props.Document));
+            SelectionManager.DeselectAll();
+            document.removeEventListener("pointermove", this.onCloseMove);
+            document.removeEventListener("pointerup", this.onCloseUp);
+        }
+    }
     onMinimizeDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
         if (e.button === 0) {
@@ -219,7 +243,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         e.stopPropagation();
     }
 
-    onLinkButtonMoved = (e: PointerEvent): void => {
+    onLinkButtonMoved = async (e: PointerEvent) => {
         if (this._linkButton.current != null) {
             document.removeEventListener("pointermove", this.onLinkButtonMoved)
             document.removeEventListener("pointerup", this.onLinkButtonUp);
@@ -234,10 +258,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
             draggedDocs.push(...draggedFromDocs);
             if (draggedDocs.length) {
-                let dragData = new DragManager.DocumentDragData(draggedDocs.map(d => {
-                    let annot = d.GetT(KeyStore.AnnotationOn, Document); // bcz: ... needs to change ... the annotationOn document may not have been retrieved yet...
-                    return annot && annot != FieldWaiting ? annot : d;
-                }));
+                let moddrag = [] as Document[];
+                for (let i = 0; i < draggedDocs.length; i++) {
+                    let doc = await draggedDocs[i].GetTAsync(KeyStore.AnnotationOn, Document);
+                    if (doc)
+                        moddrag.push(doc);
+                }
+                let dragData = new DragManager.DocumentDragData(moddrag);
                 DragManager.StartDocumentDrag([this._linkButton.current], dragData, {
                     handlers: {
                         dragComplete: action(() => { }),
@@ -400,6 +427,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }}>
                 <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
                 <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onPointerDown} onKeyPress={this.enterPressed} />
+                <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
                 <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index daeca69e2..9eee23a1d 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -128,6 +128,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
         }
         if (type.indexOf("pdf") !== -1) {
             ctor = Documents.PdfDocument;
+            options.nativeWidth = 600;
         }
         if (type.indexOf("html") !== -1) {
             if (path.includes('localhost')) {
@@ -177,7 +178,6 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
             let item = e.dataTransfer.items[i];
             if (item.kind === "string" && item.type.indexOf("uri") != -1) {
                 e.dataTransfer.items[i].getAsString(action((s: string) => {
-                    let document: Document;
                     request.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s), (err, res, body) => {
                         let type = res.headers["content-type"];
                         if (type) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3484e963e..1ddb84a99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -20,6 +20,7 @@ import React = require("react");
 import v5 = require("uuid/v5");
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import { PreviewCursor } from "./PreviewCursor";
+import { NumberField } from "../../../../fields/NumberField";
 
 @observer
 export class CollectionFreeFormView extends CollectionViewBase {
@@ -83,13 +84,17 @@ export class CollectionFreeFormView extends CollectionViewBase {
                 let dragDoc = de.data.droppedDocuments[0];
                 let dragX = dragDoc.GetNumber(KeyStore.X, 0);
                 let dragY = dragDoc.GetNumber(KeyStore.Y, 0);
-                droppedDocs.map(d => {
+                droppedDocs.map(async d => {
                     let docX = d.GetNumber(KeyStore.X, 0);
                     let docY = d.GetNumber(KeyStore.Y, 0);
                     d.SetNumber(KeyStore.X, x + (docX - dragX));
                     d.SetNumber(KeyStore.Y, y + (docY - dragY));
-                    if (!d.GetNumber(KeyStore.Width, 0)) {
+                    let docW = await d.GetTAsync(KeyStore.Width, NumberField);
+                    let docH = await d.GetTAsync(KeyStore.Height, NumberField);
+                    if (!docW) {
                         d.SetNumber(KeyStore.Width, 300);
+                    }
+                    if (!docH) {
                         d.SetNumber(KeyStore.Height, 300);
                     }
                     this.bringToFront(d);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e2239c8be..df150a045 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -135,6 +135,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this.marqueeInkDelete(inkData);
             // }, 100);
             this.cleanupInteractions();
+            SelectionManager.DeselectAll();
         }
     }
     @action
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3cb3cf782..c9fed7f66 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import * as htmlToImage from "html-to-image";
-import { action, computed, observable, reaction, IReactionDisposer, trace, keys } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css';
 import Measure from "react-measure";
@@ -10,6 +10,7 @@ import { FieldWaiting, Opt } from '../../../fields/Field';
 import { ImageField } from '../../../fields/ImageField';
 import { KeyStore } from '../../../fields/KeyStore';
 import { PDFField } from '../../../fields/PDFField';
+import { RouteStore } from "../../../server/RouteStore";
 import { Utils } from '../../../Utils';
 import { Annotation } from './Annotation';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -17,8 +18,6 @@ import "./ImageBox.scss";
 import "./PDFBox.scss";
 import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
 import React = require("react")
-import { RouteStore } from "../../../server/RouteStore";
-import { NumberField } from "../../../fields/NumberField";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
  * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, 
@@ -378,8 +377,9 @@ export class PDFBox extends React.Component<FieldViewProps> {
     saveThumbnail = () => {
         setTimeout(() => {
             var me = this;
-            htmlToImage.toPng(this._mainDiv.current!,
-                { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
+            let nwidth = me.props.doc.GetNumber(KeyStore.NativeWidth, 0);
+            let nheight = me.props.doc.GetNumber(KeyStore.NativeHeight, 0);
+            htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 })
                 .then(function (dataUrl: string) {
                     me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
                     me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1));
-- 
cgit v1.2.3-70-g09d2


From 4fde212cd00bd2f8fc2fa122309af3bb71bba2fd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 4 Apr 2019 18:18:45 -0400
Subject: improved pdf bitmaps a bit, but still blurry.

---
 src/client/documents/Documents.ts                   |  2 +-
 src/client/views/collections/CollectionViewBase.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx             |  2 +-
 src/client/views/nodes/PDFBox.tsx                   | 20 +++++++++++---------
 4 files changed, 14 insertions(+), 12 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0bf275df8..1f0744782 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -136,7 +136,7 @@ export namespace Documents {
     function GetPdfPrototype(): Document {
         if (!pdfProto) {
             pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
-                { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
+                { x: 0, y: 0, nativeWidth: 1200, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
             pdfProto.SetNumber(KeyStore.CurPage, 1);
             pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString());
         }
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 9eee23a1d..458bae7ab 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -128,7 +128,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
         }
         if (type.indexOf("pdf") !== -1) {
             ctor = Documents.PdfDocument;
-            options.nativeWidth = 600;
+            options.nativeWidth = 1200;
         }
         if (type.indexOf("html") !== -1) {
             if (path.includes('localhost')) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 714ab9447..b9329f269 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -249,7 +249,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         e.stopPropagation();
-        if (
+        if (!SelectionManager.IsSelected(this) &&
             Math.abs(e.clientX - this._downX) < 4 &&
             Math.abs(e.clientY - this._downY) < 4
         ) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c9fed7f66..f9f5bc8f8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -18,6 +18,7 @@ import "./ImageBox.scss";
 import "./PDFBox.scss";
 import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
 import React = require("react")
+import { SelectionManager } from "../../util/SelectionManager";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
  * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, 
@@ -57,6 +58,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
     private _mainDiv = React.createRef<HTMLDivElement>()
     private _pdf = React.createRef<HTMLCanvasElement>();
 
+    @observable private _renderAsSvg = true;
+
     //very useful for keeping track of X and y position throughout the PDF Canvas
     private initX: number = 0;
     private initY: number = 0;
@@ -91,9 +94,9 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     componentDidMount() {
         this._reactionDisposer = reaction(
-            () => [this.curPage, this.thumbnailPage],
+            () => [SelectionManager.SelectedDocuments().slice()],
             () => {
-                if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage) {
+                if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage && !this.props.isSelected()) {
                     this.saveThumbnail();
                     this._interactive = true;
                 }
@@ -375,19 +378,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     @action
     saveThumbnail = () => {
+        this._renderAsSvg = false;
         setTimeout(() => {
             var me = this;
             let nwidth = me.props.doc.GetNumber(KeyStore.NativeWidth, 0);
             let nheight = me.props.doc.GetNumber(KeyStore.NativeHeight, 0);
             htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 })
-                .then(function (dataUrl: string) {
+                .then(action((dataUrl: string) => {
                     me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
                     me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1));
-                })
+                    me._renderAsSvg = true;
+                }))
                 .catch(function (error: any) {
                     console.error('oops, something went wrong!', error);
                 });
-        }, 1000);
+        }, 250);
     }
 
     @action
@@ -427,9 +432,6 @@ export class PDFBox extends React.Component<FieldViewProps> {
             this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
             this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight);
         }
-        if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
-            this.saveThumbnail();
-        }
     }
 
     @computed
@@ -439,7 +441,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
         let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
         let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
         return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
-            <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode="svg">
+            <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}>
                 <Measure onResize={this.setScaling}>
                     {({ measureRef }) =>
                         <div className="pdfBox-page" ref={measureRef}>
-- 
cgit v1.2.3-70-g09d2

