From 5ffd715a2ce6c25daff22410ed9b6b009e8938d3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 7 May 2019 00:21:03 -0400
Subject: playing with templates for text.  weird stuff happens 50% of the time
 with text typing backward.

---
 src/client/views/DocumentDecorations.tsx | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 693d6ec55..e2f445874 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -78,11 +78,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 if (SelectionManager.SelectedDocuments().length > 0) {
                     let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
                     if (typeof field === "number") {
-                        SelectionManager.SelectedDocuments().forEach(d =>
-                            d.props.Document[this._fieldKey] = +this._title);
+                        SelectionManager.SelectedDocuments().forEach(d => {
+                            let doc = d.props.Document.proto ? d.props.Document.proto : d.props.Document;
+                            doc[this._fieldKey] = +this._title;
+                        });
                     } else {
-                        SelectionManager.SelectedDocuments().forEach(d =>
-                            d.props.Document[this._fieldKey] = this._title);
+                        SelectionManager.SelectedDocuments().forEach(d => {
+                            let doc = d.props.Document.proto ? d.props.Document.proto : d.props.Document;
+                            doc[this._fieldKey] = this._title;
+                        });
                     }
                 }
             }
-- 
cgit v1.2.3-70-g09d2


From 26141a697ae52a7edf3cc6845ce2153111f8860e Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 7 May 2019 15:26:29 -0400
Subject: added Bullet template

---
 src/client/views/DocumentDecorations.tsx             | 15 ++++++++++++---
 src/client/views/TemplateMenu.tsx                    | 20 +++++++++++++++-----
 src/client/views/Templates.tsx                       | 18 +++++-------------
 src/client/views/collections/CollectionBaseView.tsx  | 10 +++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx   |  9 +++++----
 5 files changed, 44 insertions(+), 28 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e2f445874..8ae71fdc8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -507,9 +507,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
 
         let templates: Map<Template, boolean> = new Map();
-        let doc = SelectionManager.SelectedDocuments()[0];
         Array.from(Object.values(Templates.TemplateList)).map(template => {
-            let docTemps = doc.templates;
+            let docTemps = SelectionManager.SelectedDocuments().reduce((res: string[], doc: DocumentView, i) => {
+                let temps = doc.props.Document.templates;
+                if (temps instanceof List) {
+                    temps.map(temp => {
+                        if (temp !== Templates.Bullet.Layout || i === 0) {
+                            res.push(temp);
+                        }
+                    })
+                }
+                return res
+            }, [] as string[]);
             let checked = false;
             docTemps.forEach(temp => {
                 if (template.Layout === temp) {
@@ -560,7 +569,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                             <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
                         </div>
                     </div>
-                    <TemplateMenu doc={doc} templates={templates} />
+                    <TemplateMenu docs={SelectionManager.SelectedDocuments()} templates={templates} />
                 </div>
             </div >
         </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f29d9c8a1..376feb5a5 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -4,6 +4,8 @@ import { observer } from "mobx-react";
 import './DocumentDecorations.scss';
 import { Template } from "./Templates";
 import { DocumentView } from "./nodes/DocumentView";
+import { List } from "../../new_fields/List";
+import { Doc } from "../../new_fields/Doc";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -25,24 +27,32 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool
 }
 
 export interface TemplateMenuProps {
-    doc: DocumentView;
+    docs: DocumentView[];
     templates: Map<Template, boolean>;
 }
 
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
-
     @observable private _hidden: boolean = true;
 
-
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
         if (event.target.checked) {
-            this.props.doc.addTemplate(template);
+            if (template.Name == "Bullet") {
+                this.props.docs[0].addTemplate(template);
+                this.props.docs[0].props.Document.maximizedDocs = new List<Doc>(this.props.docs.filter((v, i) => i !== 0).map(v => v.props.Document));
+            } else {
+                this.props.docs.map(d => d.addTemplate(template));
+            }
             this.props.templates.set(template, true);
             this.props.templates.forEach((checked, template) => console.log("Set Checked + " + checked + " " + this.props.templates.get(template)));
         } else {
-            this.props.doc.removeTemplate(template);
+            if (template.Name == "Bullet") {
+                this.props.docs[0].removeTemplate(template);
+                this.props.docs[0].props.Document.maximizedDocs = undefined;
+            } else {
+                this.props.docs.map(d => d.removeTemplate(template));
+            }
             this.props.templates.set(template, false);
             this.props.templates.forEach((checked, template) => console.log("Unset Checked + " + checked + " " + this.props.templates.get(template)));
         }
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 738272913..51fca4c41 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -54,21 +54,13 @@ export namespace Templates {
         `<div><div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div>
         <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{props.Document.title}</div></div>`
     );
-    export const Summary = new Template("Title", TemplatePosition.InnerTop,
-        `<div style="height:100%; width:100%;position:absolute; margin:0; padding: 0">
-            <div style="height:60%; width:100%;position:absolute;background:yellow; padding:0; margin:0">
-                {layout}
-            </div>
-            <div style="bottom:0px; height:40%; width:50%; position:absolute; background-color: rgba(0, 0, 0, .4); color: white;">
-                <FieldView {...props} fieldKey={"doc1"} />
-            </div>
-            <div style="bottom:0; left: 50%; height:40%; width:50%; position:absolute; background-color: rgba(0, 0, 0, .4); color: white;">
-                <FieldView {...props} fieldKey={"doc2"} />
-            </div>
-        </div>`
+
+    export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
+        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
+        <div className="isBullet" style="height:25px; width:25px; margin-left:-25px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px"/></div>`
     );
 
-    export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption];
+    export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption, Bullet];
 
     export function sortTemplates(a: Template, b: Template) {
         if (a.Position < b.Position) { return -1; }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index cbb568c07..14b92af48 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -95,15 +95,18 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         if (!this.createsCycle(doc, props.Document)) {
             //TODO This won't create the field if it doesn't already exist
             const value = Cast(props.Document[props.fieldKey], listSpec(Doc));
+            let alreadyAdded = true;
             if (value !== undefined) {
-                if (allowDuplicates || !value.some(v => v[Id] === doc[Id])) {
+                if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+                    alreadyAdded = false;
                     value.push(doc);
                 }
             } else {
+                alreadyAdded = false;
                 Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new List([doc]));
             }
             // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
-            if (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid) {
+            if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
                 let zoom = NumCast(this.props.Document.scale, 1);
                 Doc.SetOnPrototype(doc, "zoomBasis", zoom);
             }
@@ -118,7 +121,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
         let index = -1;
         for (let i = 0; i < value.length; i++) {
-            if (value[i][Id] === doc[Id]) {
+            let v = value[i];
+            if (v instanceof Doc && v[Id] === doc[Id]) {
                 index = i;
                 break;
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c4f85847a..df78d92e2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -127,6 +127,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
     @action
     public toggleIcon = async (): Promise<void> => {
+        UndoManager.GetOpenBatches().forEach(batch => console.log(batch.batchName));
         SelectionManager.DeselectAll();
         let isMinimized: boolean | undefined;
         let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
@@ -144,8 +145,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     if (isMinimized === undefined) {
                         isMinimized = BoolCast(maximizedDoc.isMinimized, false);
                     }
-                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
-                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
+                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) * this.getTransform().Scale / 2;
+                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) * this.getTransform().Scale / 2;
                     let maxx = NumCast(maximizedDoc.x, undefined);
                     let maxy = NumCast(maximizedDoc.y, undefined);
                     let maxw = NumCast(maximizedDoc.width, undefined);
@@ -153,7 +154,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
                         maxw !== undefined && maxh !== undefined) {
                         let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
-                        maximizedDoc.isMinimized = false;
+                        if (isMinimized) maximizedDoc.isMinimized = false;
                         maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0])
                     }
                 }
@@ -175,7 +176,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let altKey = e.altKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            if (BoolCast(this.props.Document.isButton, false)) {
+            if (BoolCast(this.props.Document.isButton, false) || (e.target as any).className === "isBullet") {
                 let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
-- 
cgit v1.2.3-70-g09d2


From 9573fe783fe9edfa38421d806a473d4cc496733f Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 8 May 2019 11:16:30 -0400
Subject: cleaned up template menu.  made icon doc deletable.

---
 src/client/util/UndoManager.ts                            | 5 ++++-
 src/client/views/DocumentDecorations.scss                 | 7 ++++---
 src/client/views/DocumentDecorations.tsx                  | 7 +++----
 src/client/views/TemplateMenu.tsx                         | 4 +---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 1 -
 5 files changed, 12 insertions(+), 12 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 0b5280c4a..c0ed015bd 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,6 @@
 import { observable, action, runInAction } from "mobx";
 import 'source-map-support/register';
 import { Without } from "../../Utils";
-import { string } from "prop-types";
 
 function getBatchName(target: any, key: string | symbol): string {
     let keyName = key.toString();
@@ -94,6 +93,10 @@ export namespace UndoManager {
         return redoStack.length > 0;
     }
 
+    export function PrintBatches(): void {
+        GetOpenBatches().forEach(batch => console.log(batch.batchName));
+    }
+
     let openBatches: Batch[] = [];
     export function GetOpenBatches(): Without<Batch, 'end'>[] {
         return openBatches;
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 158b02b5a..6a2e33836 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -210,14 +210,15 @@ $linkGap : 3px;
     position: absolute;
     top: 0;
     left: 30px;
-    width: 150px;
-    line-height: 25px;
-    max-height: 175px;
+    width: max-content;
     font-family: $sans-serif;
     font-size: 12px;
     background-color: $light-color-secondary;
     padding: 2px 12px;
     list-style: none;
+    .templateToggle {
+        text-align: left;
+    }
 
     input {
         margin-right: 10px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8ae71fdc8..e3eb034fa 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -29,6 +29,7 @@ import { CollectionFreeFormView } from "./collections/collectionFreeForm/Collect
 import { CollectionView } from "./collections/CollectionView";
 import { createCipher } from "crypto";
 import { FieldView } from "./nodes/FieldView";
+import { DocumentManager } from "../util/DocumentManager";
 
 library.add(faLink);
 
@@ -277,13 +278,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     public getIconDoc = async (docView: DocumentView): Promise<Doc | undefined> => {
         let doc = docView.props.Document;
         let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
-        if (!iconDoc) {
+
+        if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) {
             const layout = StrCast(doc.backgroundLayout, StrCast(doc.layout, FieldView.LayoutString(DocumentView)));
             iconDoc = this.createIcon([docView], layout);
         }
-        if (SelectionManager.SelectedDocuments()[0].props.addDocument !== undefined) {
-            SelectionManager.SelectedDocuments()[0].props.addDocument!(iconDoc!);
-        }
         return iconDoc;
     }
     moveIconDoc(iconDoc: Doc) {
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 376feb5a5..d74982ef8 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -15,7 +15,7 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool
     render() {
         if (this.props.template) {
             return (
-                <li>
+                <li className="templateToggle">
                     <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
                     {this.props.template.Name}
                 </li>
@@ -45,7 +45,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 this.props.docs.map(d => d.addTemplate(template));
             }
             this.props.templates.set(template, true);
-            this.props.templates.forEach((checked, template) => console.log("Set Checked + " + checked + " " + this.props.templates.get(template)));
         } else {
             if (template.Name == "Bullet") {
                 this.props.docs[0].removeTemplate(template);
@@ -54,7 +53,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 this.props.docs.map(d => d.removeTemplate(template));
             }
             this.props.templates.set(template, false);
-            this.props.templates.forEach((checked, template) => console.log("Unset Checked + " + checked + " " + this.props.templates.get(template)));
         }
     }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5efc75793..a9c53933e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -127,7 +127,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
     @action
     public toggleIcon = async (): Promise<void> => {
-        UndoManager.GetOpenBatches().forEach(batch => console.log(batch.batchName));
         SelectionManager.DeselectAll();
         let isMinimized: boolean | undefined;
         let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
-- 
cgit v1.2.3-70-g09d2


From 244d4d127e7e1a0faadbc5a8baed7922ef03522c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 14:11:35 -0400
Subject: cleaned up textlinking a little.

---
 src/client/util/DragManager.ts                     | 13 ++--
 src/client/util/SelectionManager.ts                |  4 +-
 src/client/util/TooltipTextMenu.tsx                | 25 +++---
 src/client/views/DocumentDecorations.tsx           |  8 +-
 src/client/views/MainOverlayTextBox.tsx            | 30 ++++---
 .../views/collections/CollectionSchemaView.tsx     |  3 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +-
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 91 ++++++++++++++++++----
 10 files changed, 122 insertions(+), 64 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index fbf20e244..eaf851a75 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,11 +1,9 @@
-import { action } from "mobx";
+import { action, runInAction } from "mobx";
+import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Cast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
-import { Doc, DocListCast } from "../../new_fields/Doc";
-import { Cast } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
 
 export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Doc, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
@@ -154,7 +152,10 @@ export namespace DragManager {
         [id: string]: any;
     }
 
+    export let StartDragFunctions: (() => void)[] = [];
+
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+        runInAction(() => StartDragFunctions.map(func => func()));
         StartDrag(eles, dragData, downX, downY, options,
             (dropData: { [id: string]: any }) =>
                 (dropData.droppedDocuments = dragData.userDropAction == "alias" || (!dragData.userDropAction && dragData.dropAction == "alias") ?
@@ -189,7 +190,6 @@ export namespace DragManager {
             dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        if (!dragData.dontClearTextBox) MainOverlayTextBox.Instance.SetTextDoc();
 
         let scaleXs: number[] = [];
         let scaleYs: number[] = [];
@@ -217,6 +217,7 @@ export namespace DragManager {
             dragElement.style.top = "0";
             dragElement.style.bottom = "";
             dragElement.style.left = "0";
+            dragElement.style.color = "black";
             dragElement.style.transformOrigin = "0 0";
             dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
             dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index fe5acf4b4..a3a8172c7 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,7 +1,7 @@
 import { observable, action } from "mobx";
 import { Doc } from "../../new_fields/Doc";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
 import { DocumentView } from "../views/nodes/DocumentView";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 export namespace SelectionManager {
     class Manager {
@@ -25,7 +25,7 @@ export namespace SelectionManager {
         DeselectAll(): void {
             manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments = [];
-            MainOverlayTextBox.Instance.SetTextDoc();
+            FormattedTextBox.InputBoxOverlay = undefined;
         }
         @action
         ReselectAll() {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 15895fd1c..6eb654319 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -48,6 +48,7 @@ export class TooltipTextMenu {
     private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
     private linkEditor?: HTMLDivElement;
     private linkText?: HTMLDivElement;
+    private linkDrag?: HTMLImageElement;
     //dropdown doms
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
@@ -168,8 +169,13 @@ export class TooltipTextMenu {
             this.linkText = document.createElement("div");
             this.linkText.style.cssFloat = "left";
             this.linkText.style.marginRight = "5px";
+            this.linkText.style.marginLeft = "5px";
             this.linkText.setAttribute("contenteditable", "true");
+            this.linkText.style.whiteSpace = "nowrap";
+            this.linkText.style.width = "150px";
+            this.linkText.style.overflow = "hidden";
             this.linkText.style.color = "white";
+            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }
             let linkBtn = document.createElement("div");
             linkBtn.textContent = ">>";
             linkBtn.style.width = "20px";
@@ -196,16 +202,17 @@ export class TooltipTextMenu {
                     e.preventDefault();
                 }
             }
-            let linkDrag = document.createElement("div");
-            linkDrag.textContent = "O";
-            linkDrag.style.width = "20px";
-            linkDrag.style.height = "20px";
-            linkDrag.style.color = "white";
-            linkDrag.style.cssFloat = "left";
-            linkDrag.onpointerdown = (e: PointerEvent) => {
+            this.linkDrag = document.createElement("img");
+            this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
+            this.linkDrag.style.width = "20px";
+            this.linkDrag.style.height = "20px";
+            this.linkDrag.style.color = "white";
+            this.linkDrag.style.background = "black";
+            this.linkDrag.style.cssFloat = "left";
+            this.linkDrag.onpointerdown = (e: PointerEvent) => {
                 let dragData = new DragManager.LinkDragData(this.editorProps.Document);
                 dragData.dontClearTextBox = true;
-                DragManager.StartLinkDrag(this.linkEditor!, dragData, e.clientX, e.clientY,
+                DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
                     {
                         handlers: {
                             dragComplete: action(() => {
@@ -216,9 +223,9 @@ export class TooltipTextMenu {
                         hideSource: false
                     })
             };
+            this.linkEditor.appendChild(this.linkDrag);
             this.linkEditor.appendChild(this.linkText);
             this.linkEditor.appendChild(linkBtn);
-            this.linkEditor.appendChild(linkDrag)
             this.tooltip.appendChild(this.linkEditor);
         }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e3eb034fa..85ddadd64 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,7 +5,6 @@ import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import './DocumentDecorations.scss';
-import { MainOverlayTextBox } from "./MainOverlayTextBox";
 import { DocumentView, PositionDocument } from "./nodes/DocumentView";
 import { LinkMenu } from "./nodes/LinkMenu";
 import { TemplateMenu } from "./TemplateMenu";
@@ -25,11 +24,9 @@ import { faLink } from '@fortawesome/free-solid-svg-icons';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
-import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionView } from "./collections/CollectionView";
-import { createCipher } from "crypto";
-import { FieldView } from "./nodes/FieldView";
 import { DocumentManager } from "../util/DocumentManager";
+import { FormattedTextBox } from "./nodes/FormattedTextBox";
 
 library.add(faLink);
 
@@ -328,6 +325,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             let selDoc = SelectionManager.SelectedDocuments()[0];
             let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.proto : undefined;
             let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
+            FormattedTextBox.InputBoxOverlay = undefined;
             DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
                 handlers: {
                     dragComplete: action(emptyFunction),
@@ -410,7 +408,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 break;
         }
 
-        MainOverlayTextBox.Instance.SetTextDoc();
+        FormattedTextBox.InputBoxOverlay = undefined;
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 3b75c248a..91f626737 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -1,16 +1,12 @@
-import { action, observable, trace } from 'mobx';
+import { action, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import "normalize.css";
 import * as React from 'react';
 import { emptyFunction, returnTrue, returnZero } from '../../Utils';
-import '../northstar/model/ModelExtensions';
-import '../northstar/utils/Extensions';
 import { DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
+import "normalize.css";
 import "./MainOverlayTextBox.scss";
 import { FormattedTextBox } from './nodes/FormattedTextBox';
-import { Doc } from '../../new_fields/Doc';
-import { NumCast } from '../../new_fields/Types';
 
 interface MainOverlayTextBoxProps {
 }
@@ -18,8 +14,6 @@ interface MainOverlayTextBoxProps {
 @observer
 export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
     public static Instance: MainOverlayTextBox;
-    @observable public TextDoc?: Doc = undefined;
-    public TextScroll: number = 0;
     @observable _textXf: () => Transform = () => Transform.Identity();
     private _textFieldKey: string = "data";
     private _textColor: string | null = null;
@@ -30,15 +24,18 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         super(props);
         this._textProxyDiv = React.createRef();
         MainOverlayTextBox.Instance = this;
+        reaction(() => FormattedTextBox.InputBoxOverlay,
+            (box?: FormattedTextBox) => {
+                if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, box.props.ScreenToLocalTransform);
+                else this.setTextDoc();
+            });
     }
 
     @action
-    SetTextDoc(textDoc?: Doc, textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) {
+    private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) {
         if (this._textTargetDiv) {
             this._textTargetDiv.style.color = this._textColor;
         }
-
-        this.TextDoc = textDoc;
         this._textFieldKey = textFieldKey!;
         this._textXf = tx ? tx : () => Transform.Identity();
         this._textTargetDiv = div;
@@ -46,15 +43,13 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             if (div.parentElement && div.parentElement instanceof HTMLDivElement && div.parentElement.id === "screenSpace") this._textXf = () => Transform.Identity();
             this._textColor = div.style.color;
             div.style.color = "transparent";
-            this.TextScroll = div.scrollTop;
         }
     }
 
     @action
     textScroll = (e: React.UIEvent) => {
         if (this._textProxyDiv.current && this._textTargetDiv) {
-            this.TextScroll = (e as any)._targetInst.stateNode.scrollTop;//  this._textProxyDiv.current.children[0].scrollTop;
-            this._textTargetDiv.scrollTop = this.TextScroll;
+            this._textTargetDiv.scrollTop = (e as any)._targetInst.stateNode.scrollTop;
         }
     }
 
@@ -64,11 +59,12 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             document.addEventListener('pointerup', this.textBoxUp);
         }
     }
+    @action
     textBoxMove = (e: PointerEvent) => {
         if (e.movementX > 1 || e.movementY > 1) {
             document.removeEventListener("pointermove", this.textBoxMove);
             document.removeEventListener('pointerup', this.textBoxUp);
-            let dragData = new DragManager.DocumentDragData([this.TextDoc!]);
+            let dragData = new DragManager.DocumentDragData(FormattedTextBox.InputBoxOverlay ? [FormattedTextBox.InputBoxOverlay.props.Document] : []);
             const [left, top] = this._textXf().inverse().transformPoint(0, 0);
             dragData.xOffset = e.clientX - left;
             dragData.yOffset = e.clientY - top;
@@ -86,13 +82,13 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     }
 
     render() {
-        if (this.TextDoc && this._textTargetDiv) {
+        if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
             let textRect = this._textTargetDiv.getBoundingClientRect();
             let s = this._textXf().Scale;
             return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
-                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
                         selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
                         ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} />
                 </div>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 16818affd..6dd0e5935 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -322,8 +322,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     render() {
         library.add(faCog);
         library.add(faPlus);
-        //This can't just pass FieldValue to filter because filter passes other arguments to the passed in function, which end up as default values in FieldValue
-        const children = (this.children || []).filter(doc => FieldValue(doc));
+        const children = this.children;
         return (
             <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
                 onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 828ac880a..082692d8d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -50,7 +50,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get children() {
             //TODO tfs: This might not be what we want?
             //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc));
+            return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc)).map(doc => doc as Doc);
         }
 
         @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 797f94d5f..c1d149098 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -70,7 +70,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     public getActiveDocuments = () => {
         const curPage = FieldValue(this.Document.curPage, -1);
-        return FieldValue(this.children, [] as Doc[]).filter(doc => {
+        return this.children.filter(doc => {
             var page = NumCast(doc.page, -1);
             return page === curPage || page === -1;
         });
@@ -169,7 +169,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // if (!this.props.active()) {
         //     return;
         // }
-        let childSelected = (this.children || []).filter(doc => doc).some(doc => {
+        let childSelected = this.children.some(doc => {
             var dv = DocumentManager.Instance.getDocumentView(doc);
             return dv && SelectionManager.IsSelected(dv) ? true : false;
         });
@@ -222,7 +222,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     bringToFront = (doc: Doc) => {
-        const docs = (this.children || []);
+        const docs = this.children;
         docs.slice().sort((doc1, doc2) => {
             if (doc1 === doc) return 1;
             if (doc2 === doc) return -1;
@@ -268,7 +268,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed.struct
     get views() {
         let curPage = FieldValue(this.Document.curPage, -1);
-        let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => {
+        let docviews = this.children.reduce((prev, doc) => {
             if (!(doc instanceof Doc)) return prev;
             var page = NumCast(doc.page, -1);
             if (page === curPage || page === -1) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 613c24fa4..cdc1bdc85 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,6 +1,6 @@
 import React = require("react");
 import { observer } from "mobx-react";
-import { computed } from "mobx";
+import { computed, observable } from "mobx";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
 import { VideoBox } from "./VideoBox";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 445a834ee..e7fb94777 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,29 +1,31 @@
-import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx";
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { EditorState, Plugin, Transaction } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
+import { Doc, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { DocServer } from "../../DocServer";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorKeymap";
 import { inpRules } from "../../util/RichTextRules";
-import { schema, ImageResizeView } from "../../util/RichTextSchema";
+import { ImageResizeView, schema } from "../../util/RichTextSchema";
+import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { ContextMenu } from "../../views/ContextMenu";
-import { MainOverlayTextBox } from "../MainOverlayTextBox";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
+import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
 import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
-import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Opt, Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
-import { observer } from "mobx-react";
-import { InkingControl } from "../InkingControl";
-import { StrCast, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { Id } from "../../../new_fields/RefField";
-import { UndoManager } from "../../util/UndoManager";
 
 // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
 //
@@ -62,15 +64,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _proseRef: React.RefObject<HTMLDivElement>;
     private _editorView: Opt<EditorView>;
     private _gotDown: boolean = false;
+    private _dropDisposer?: DragManager.DragDropDisposer;
     private _reactionDisposer: Opt<IReactionDisposer>;
     private _inputReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
+    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
+    @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
+    public static InputBoxOverlayScroll: number = 0;
 
     constructor(props: FieldViewProps) {
         super(props);
 
         this._ref = React.createRef();
         this._proseRef = React.createRef();
+        if (this.props.isOverlay) {
+            DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
+        }
     }
 
     _applyingChange: boolean = false;
@@ -94,7 +104,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    @undoBatch
+    @action
+    drop = async (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.LinkDragData) {
+            let sourceDoc = de.data.linkSourceDocument;
+            let destDoc = this.props.Document;
+
+            const protoDest = destDoc.proto;
+            const protoSrc = sourceDoc.proto;
+            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            de.data.droppedDocuments.push(destDoc);
+            e.stopPropagation();
+        }
+    }
+
     componentDidMount() {
+        if (this._ref.current) {
+            this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, {
+                handlers: { drop: this.drop.bind(this) }
+            });
+        }
         const config = {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
@@ -117,7 +147,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         };
 
         if (this.props.isOverlay) {
-            this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc[Id],
+            this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay,
                 () => {
                     if (this._editorView) {
                         this._editorView.destroy();
@@ -127,7 +157,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             );
         } else {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
-                () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
+                () => {
+                    if (this.props.isSelected()) {
+                        FormattedTextBox.InputBoxOverlay = this;
+                        FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop;
+                    }
+                });
         }
 
 
@@ -178,6 +213,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this._proxyReactionDisposer) {
             this._proxyReactionDisposer();
         }
+        if (this._dropDisposer) {
+            this._dropDisposer();
+        }
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
@@ -186,6 +224,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
                 this._toolTipTextMenu.tooltip.style.opacity = "0";
         }
+        if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey) {
+            if (e.target && (e.target as any).href) {
+                let href = (e.target as any).href;
+                if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+                    let docid = href.replace(DocServer.prepend("/doc/"), "");
+                    DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                        if (f instanceof Doc) {
+                            if (DocumentManager.Instance.getDocumentView(f))
+                                DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+                            else CollectionDockingView.Instance.AddRightSplit(f);
+                        }
+                    }));
+                }
+                e.stopPropagation();
+                e.preventDefault();
+            }
+
+        }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             this._gotDown = true;
             e.preventDefault();
@@ -199,12 +255,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    @action
     onFocused = (e: React.FocusEvent): void => {
         if (!this.props.isOverlay) {
-            MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
+            FormattedTextBox.InputBoxOverlay = this;
         } else {
             if (this._proseRef.current) {
-                this._proseRef.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
+                this._proseRef.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
             }
         }
     }
-- 
cgit v1.2.3-70-g09d2


From b83cfb1c48cced31f930e3f72a9c5ae503b81790 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 14:15:42 -0400
Subject: from last

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

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 85ddadd64..2bde4e0c8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -318,6 +318,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         e.stopPropagation();
     }
 
+    @action
     onLinkerButtonMoved = (e: PointerEvent): void => {
         if (this._linkerButton.current !== null) {
             document.removeEventListener("pointermove", this.onLinkerButtonMoved);
-- 
cgit v1.2.3-70-g09d2


From 168cd36282087bbf9e0157352a129d90b20b7394 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 14:40:03 -0400
Subject: fixed some compile errors

---
 src/client/northstar/dash-nodes/HistogramBox.tsx      | 8 ++++----
 src/client/util/DocumentManager.ts                    | 2 +-
 src/client/views/DocumentDecorations.tsx              | 1 +
 src/client/views/collections/CollectionBaseView.tsx   | 4 ++--
 src/client/views/collections/CollectionSchemaView.tsx | 2 +-
 src/client/views/nodes/FieldView.tsx                  | 3 ++-
 src/client/views/nodes/LinkMenu.tsx                   | 4 ++--
 7 files changed, 13 insertions(+), 11 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 765ecf8f0..5e7b867b3 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -117,15 +117,15 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.HistoOp : HistogramOperation.Empty;
                 if (this.HistoOp !== HistogramOperation.Empty) {
-                    reaction(() => Cast(this.props.Document.linkedFromDocs, listSpec(Doc), []), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => Cast(this.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
                     reaction(() => Cast(this.props.Document.brushingDocs, listSpec(Doc), []).length,
                         () => {
-                            let brushingDocs = Cast(this.props.Document.brushingDocs, listSpec(Doc), []);
+                            let brushingDocs = Cast(this.props.Document.brushingDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
                             const proto = this.props.Document.proto;
                             if (proto) {
                                 this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
-                                    brush.bckgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
-                                    let brushed = Cast(brush.brushingDocs, listSpec(Doc), []);
+                                    brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
+                                    let brushed = Cast(brush.brushingDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
                                     return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
                                 }));
                             }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 69964e2c9..4c264c7ec 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -71,7 +71,7 @@ export class DocumentManager {
     @computed
     public get LinkedDocumentViews() {
         return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
-            let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc));
+            let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
             if (linksList && linksList.length) {
                 pairs.push(...linksList.reduce((pairs, link) => {
                     if (link) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2bde4e0c8..705e7a6d8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -27,6 +27,7 @@ import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { CollectionView } from "./collections/CollectionView";
 import { DocumentManager } from "../util/DocumentManager";
 import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FieldView } from "./nodes/FieldView";
 
 library.add(faLink);
 
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 14b92af48..2b1f7bb37 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -63,13 +63,13 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         if (!(documentToAdd instanceof Doc)) {
             return false;
         }
-        let data = Cast(documentToAdd.data, listSpec(Doc), []);
+        let data = Cast(documentToAdd.data, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         for (const doc of data.filter(d => d instanceof Document)) {
             if (this.createsCycle(doc, containerDocument)) {
                 return true;
             }
         }
-        let annots = Cast(documentToAdd.annotations, listSpec(Doc), []);
+        let annots = Cast(documentToAdd.annotations, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         for (const annot of annots) {
             if (this.createsCycle(annot, containerDocument)) {
                 return true;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 6dd0e5935..ae949b2ed 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -276,7 +276,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     }
 
     get documentKeysCheckList() {
-        const docs = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+        const docs = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         let keys: { [key: string]: boolean } = {};
         // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
         //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index cdc1bdc85..8bdf34181 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -99,7 +99,8 @@ export class FieldView extends React.Component<FieldViewProps> {
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
                     toggleMinimized={emptyFunction}
-                    whenActiveChanged={this.props.whenActiveChanged} />
+                    whenActiveChanged={this.props.whenActiveChanged}
+                    bringToFront={emptyFunction} />
             );
         }
         else if (field instanceof List) {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index e21adebbc..24901913d 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -31,8 +31,8 @@ export class LinkMenu extends React.Component<Props> {
 
     render() {
         //get list of links from document
-        let linkFrom: Doc[] = Cast(this.props.docView.props.Document.linkedFromDocs, listSpec(Doc), []);
-        let linkTo: Doc[] = Cast(this.props.docView.props.Document.linkedToDocs, listSpec(Doc), []);
+        let linkFrom = Cast(this.props.docView.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+        let linkTo = Cast(this.props.docView.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         if (this._editingLink === undefined) {
             return (
                 <div id="linkMenu-container">
-- 
cgit v1.2.3-70-g09d2


From 39fd912fd4cd33f30a943290295a59992b9868eb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 May 2019 17:39:44 -0400
Subject: various cleanup to icons, summaries...

---
 src/client/util/DocumentManager.ts                 | 18 ++++++++++++--
 src/client/util/SelectionManager.ts                | 17 +++++++++++++
 src/client/views/DocumentDecorations.tsx           | 28 ++++++++++++++++++----
 src/client/views/TemplateMenu.tsx                  | 16 +++++++++----
 .../CollectionFreeFormLinkView.scss                |  8 +++----
 .../CollectionFreeFormLinkView.tsx                 |  6 ++---
 .../CollectionFreeFormLinksView.tsx                |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 17 ++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 17 ++++++-------
 src/client/views/nodes/DocumentView.tsx            | 14 +++++++----
 10 files changed, 104 insertions(+), 39 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4c264c7ec..779b07ce5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,8 +1,9 @@
 import { computed, observable } from 'mobx';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { Doc } from '../../new_fields/Doc';
-import { FieldValue, Cast } from '../../new_fields/Types';
+import { FieldValue, Cast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
+import { SelectionManager } from './SelectionManager';
 
 
 export class DocumentManager {
@@ -70,7 +71,7 @@ export class DocumentManager {
 
     @computed
     public get LinkedDocumentViews() {
-        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
+        return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
             let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
             if (linksList && linksList.length) {
                 pairs.push(...linksList.reduce((pairs, link) => {
@@ -84,6 +85,19 @@ export class DocumentManager {
                     return pairs;
                 }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
             }
+            linksList = Cast(dv.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+            if (linksList && linksList.length) {
+                pairs.push(...linksList.reduce((pairs, link) => {
+                    if (link) {
+                        let linkFromDoc = FieldValue(Cast(link.linkedFrom, Doc));
+                        if (linkFromDoc) {
+                            DocumentManager.Instance.getDocumentViews(linkFromDoc).map(docView1 =>
+                                pairs.push({ a: dv, b: docView1, l: link }));
+                        }
+                    }
+                    return pairs;
+                }, pairs));
+            }
             return pairs;
         }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
     }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index a3a8172c7..8c92c2023 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,6 +2,7 @@ import { observable, action } from "mobx";
 import { Doc } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { NumCast } from "../../new_fields/Types";
 
 export namespace SelectionManager {
     class Manager {
@@ -68,4 +69,20 @@ export namespace SelectionManager {
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments;
     }
+    export function ViewsSortedVertically(): DocumentView[] {
+        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+            if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+            if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+            return 0;
+        });
+        return sorted;
+    }
+    export function ViewsSortedHorizontally(): DocumentView[] {
+        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+            if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
+            if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
+            return 0;
+        });
+        return sorted;
+    }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 705e7a6d8..4786b4de6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -249,7 +249,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) {
                 selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
             }
-            !this._removeIcon && selectedDocs.length === 1 && this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+            if (!this._removeIcon) {
+                if (selectedDocs.length === 1)
+                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+                else {
+                    let docViews = SelectionManager.ViewsSortedVertically();
+                    let topDocView = docViews[0];
+                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+                    if (ind !== -1) {
+                        topDocView.templates.splice(ind, 1);
+                        topDocView.props.Document.subBulletDocs = undefined;
+                    } else {
+                        topDocView.addTemplate(Templates.Bullet);
+                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+                    }
+                }
+            }
             this._removeIcon = false;
         }
         runInAction(() => this._minimizedX = this._minimizedY = 0);
@@ -410,7 +425,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 break;
         }
 
-        FormattedTextBox.InputBoxOverlay = undefined;
+        runInAction(() => FormattedTextBox.InputBoxOverlay = undefined);
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
@@ -507,7 +522,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         let templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template => {
-            let docTemps = SelectionManager.SelectedDocuments().reduce((res: string[], doc: DocumentView, i) => {
+            let sorted = SelectionManager.ViewsSortedVertically().slice().sort((doc1, doc2) => {
+                if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+                if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+                return 0;
+            });
+            let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => {
                 let temps = doc.props.Document.templates;
                 if (temps instanceof List) {
                     temps.map(temp => {
@@ -568,7 +588,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                             <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
                         </div>
                     </div>
-                    <TemplateMenu docs={SelectionManager.SelectedDocuments()} templates={templates} />
+                    <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
                 </div>
             </div >
         </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index d74982ef8..e2b3bd07a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -6,6 +6,7 @@ import { Template } from "./Templates";
 import { DocumentView } from "./nodes/DocumentView";
 import { List } from "../../new_fields/List";
 import { Doc } from "../../new_fields/Doc";
+import { NumCast } from "../../new_fields/Types";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -35,20 +36,27 @@ export interface TemplateMenuProps {
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
 
+    constructor(props: TemplateMenuProps) {
+        super(props);
+        console.log("");
+    }
+
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
         if (event.target.checked) {
             if (template.Name == "Bullet") {
-                this.props.docs[0].addTemplate(template);
-                this.props.docs[0].props.Document.maximizedDocs = new List<Doc>(this.props.docs.filter((v, i) => i !== 0).map(v => v.props.Document));
+                let topDocView = this.props.docs[0];
+                topDocView.addTemplate(template);
+                topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document));
             } else {
                 this.props.docs.map(d => d.addTemplate(template));
             }
             this.props.templates.set(template, true);
         } else {
             if (template.Name == "Bullet") {
-                this.props.docs[0].removeTemplate(template);
-                this.props.docs[0].props.Document.maximizedDocs = undefined;
+                let topDocView = this.props.docs[0];
+                topDocView.removeTemplate(template);
+                topDocView.props.Document.subBulletDocs = undefined;
             } else {
                 this.props.docs.map(d => d.removeTemplate(template));
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 3e8a8a442..737ffba7d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -1,12 +1,12 @@
 .collectionfreeformlinkview-linkLine {
     stroke: black;
-    stroke-width: 3;
     transform: translate(10000px,10000px);
+    opacity: 0.5;
     pointer-events: all;
 }
 .collectionfreeformlinkview-linkCircle {
-    stroke: black;
-    stroke-width: 3;
+    stroke: rgb(0,0,0);
+    opacity: 0.5;
     transform: translate(10000px,10000px);
     pointer-events: all;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 3b700b053..63d2f7642 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -47,11 +47,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         return (
             <>
                 <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
-                    style={{ strokeWidth: `${l.length * 5}` }}
+                    style={{ strokeWidth: `${l.length / 2}` }}
                     x1={`${x1}`} y1={`${y1}`}
                     x2={`${x2}`} y2={`${y2}`} />
-                <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
-                    cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={10} onPointerDown={this.onPointerDown} />
+                <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkCircle"
+                    cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={5} onPointerDown={this.onPointerDown} />
             </>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index cbfbb1d2c..1d4584cfe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -84,7 +84,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
         }
         if (view.props.ContainingCollectionView) {
             let collid = view.props.ContainingCollectionView.props.Document[Id];
-            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).
+            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc).
                 filter(child =>
                     child[Id] === collid).map(view =>
                         DocumentManager.Instance.getDocumentViews(view).map(view =>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6057aaeba..d8855fe66 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -96,7 +96,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             document.addEventListener("pointermove", this.onPointerMove, true);
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
-            e.stopPropagation();
+            // bcz: do we need this?   it kills the context menu on the main collection
+            // e.stopPropagation();
         }
         if (e.altKey) {
             e.preventDefault();
@@ -228,14 +229,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     newCollection.proto!.summaryDoc = summary;
                     selected = [newCollection];
                 }
-                summary.proto!.maximizedDocs = new List<Doc>(selected);
+                summary.proto!.summarizedDocs = new List<Doc>(selected);
                 summary.proto!.isButton = true;
-                selected.map(maximizedDoc => {
-                    let maxx = NumCast(maximizedDoc.x, undefined);
-                    let maxy = NumCast(maximizedDoc.y, undefined);
-                    let maxw = NumCast(maximizedDoc.width, undefined);
-                    let maxh = NumCast(maximizedDoc.height, undefined);
-                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
+                selected.map(summarizedDoc => {
+                    let maxx = NumCast(summarizedDoc.x, undefined);
+                    let maxy = NumCast(summarizedDoc.y, undefined);
+                    let maxw = NumCast(summarizedDoc.width, undefined);
+                    let maxh = NumCast(summarizedDoc.height, undefined);
+                    summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
                 });
                 this.props.addLiveTextDocument(summary);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 470c0c2f8..92033ea44 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,7 +6,7 @@ import "./DocumentView.scss";
 import React = require("react");
 import { DocComponent } from "../DocComponent";
 import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
+import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Utils } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Doc, DocListCast, HeightSym } from "../../../new_fields/Doc";
@@ -65,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
-    toggleMinimized = () => this.toggleIcon();
+    toggleMinimized = async () => this.toggleIcon(await DocListCast(this.props.Document.maximizedDocs));
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@@ -126,10 +126,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             2);
     }
     @action
-    public toggleIcon = async (): Promise<void> => {
+    public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
         SelectionManager.DeselectAll();
         let isMinimized: boolean | undefined;
-        let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
         let minimizedDoc: Doc | undefined = this.props.Document;
         if (!maximizedDocs) {
             minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
@@ -177,8 +176,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let altKey = e.altKey;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            if (BoolCast(this.props.Document.isButton, false) || (e.target as any).id === "isBullet") {
-                let maximizedDocs = await DocListCast(this.props.Document.maximizedDocs);
+            let isBullet = (e.target as any).id === "isBullet";
+            let isIcon = StrCast(this.props.Document.layout).indexOf("IconBox") !== -1;
+            if (BoolCast(this.props.Document.isButton, false) || isBullet) {
+                let maximizedDocs = await DocListCast(isBullet ? this.props.Document.subBulletDocs : isIcon ? this.props.Document.maximizedDocs : this.props.Document.summarizedDocs);
                 if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
                         let dataDocs = await DocListCast(CollectionDockingView.Instance.props.Document.data);
@@ -195,7 +196,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         }
                     } else {
                         this.props.addDocument && maximizedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
-                        this.toggleIcon();
+                        this.toggleIcon(maximizedDocs);
                     }
                 }
             }
@@ -230,7 +231,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return (
             <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
                 onPointerDown={this.onPointerDown}
-                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
                 onClick={this.onClick}
                 style={{
                     outlineColor: "black",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c3bf36553..25a75904b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -18,7 +18,7 @@ import "./DocumentView.scss";
 import React = require("react");
 import { Opt, Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
 import { FieldValue, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -107,6 +107,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 handlers: { drop: this.drop.bind(this) }
             });
         }
+        // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
         this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             async () => {
                 let maxDoc = await DocListCast(this.props.Document.maximizedDocs);
@@ -142,10 +143,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         e.stopPropagation();
     }
 
-    startDragging(x: number, y: number, dropAction: dropActionType) {
+    startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
         if (this._mainCont.current) {
+            let allConnected = dragSubBullets ? [this.props.Document, ...Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc)] : [this.props.Document];
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
-            let dragData = new DragManager.DocumentDragData([this.props.Document]);
+            let dragData = new DragManager.DocumentDragData(allConnected);
             const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.xOffset = xoff;
@@ -167,15 +169,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             SelectionManager.SelectDoc(this, e.ctrlKey);
         }
     }
+    _hitIsBullet = false;
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
         if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
             return;
         }
+        this._hitIsBullet = (e.target && (e.target as any).id === "isBullet");
         if (e.shiftKey && e.buttons === 1) {
             if (this.props.isTopMost) {
-                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined);
+                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitIsBullet);
             } else if (this.props.Document) {
                 CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
             }
@@ -193,7 +197,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
                 if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
-                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined);
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitIsBullet);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-- 
cgit v1.2.3-70-g09d2

