From 55c382979c8313a35aa2f3f86d37f4b39bb9591f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 8 Feb 2020 12:58:31 -0500
Subject: fixed link button issues with text selection links

---
 .../collections/collectionFreeForm/CollectionFreeFormLinkView.tsx     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index b8fbaef5c..f04b79ea4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -68,8 +68,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                         (this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
                         const m = targetAhyperlink.getBoundingClientRect();
                         const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-                        this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
-                        this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
+                        this.props.B.props.Document[bfield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
+                        this.props.B.props.Document[bfield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
                     }, 0);
                 }
             })
-- 
cgit v1.2.3-70-g09d2


From 72892f0811f03dae096ebea2dcac24b56d661b07 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 20 Feb 2020 23:45:59 -0500
Subject: more link editor fixes.  fixed border rounding.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../CollectionFreeFormLinkView.tsx                 |  9 ++-
 src/client/views/linking/LinkEditor.scss           | 22 ++++---
 src/client/views/linking/LinkEditor.tsx            | 73 +++-------------------
 src/client/views/linking/LinkMenu.tsx              |  2 +-
 src/client/views/linking/LinkMenuItem.tsx          | 38 +++--------
 7 files changed, 39 insertions(+), 109 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d2de54b7..9c83a6a9e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -85,7 +85,7 @@ export interface DocumentOptions {
     y?: number;
     z?: number;
     dropAction?: dropActionType;
-    chilDropAction?: dropActionType;
+    childDropAction?: dropActionType;
     layoutKey?: string;
     type?: string;
     title?: string;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 61c199c1d..dcd8dd113 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -256,7 +256,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
         dist = dist < 3 ? 0 : dist;
         SelectionManager.SelectedDocuments().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
-            map(d => d.borderRounding = `${Math.min(100, dist)}%`);
+            map(d => d.borderRounding = `${Math.max(0, dist)}px`);
         return false;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f04b79ea4..f0c706c1e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -95,10 +95,15 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const pt2 = [bpt.point.x, bpt.point.y];
         const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
         const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
-        return !aActive && !bActive ? (null) :
+        const text = StrCast(this.props.A.props.Document.title);
+        return !aActive && !bActive ? (null) : (<>
+            <text x={(pt1[0] + pt2[0]) / 2} y={(pt1[1] + pt2[1]) / 2}>
+                {text !== "-ungrouped-" ? text : ""}
+            </text>
             <line key="linkLine" className="collectionfreeformlinkview-linkLine"
                 style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
                 x1={`${pt1[0]}`} y1={`${pt1[1]}`}
-                x2={`${pt2[0]}`} y2={`${pt2[1]}`} />;
+                x2={`${pt2[0]}`} y2={`${pt2[1]}`} />
+        </>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index fc5f2410c..0807216c9 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -22,10 +22,9 @@
     }
 }
 
-.linkEditor-button {
-    width: 20px;
-    height: 20px;
-    margin-left: 6px;
+.linkEditor-button, .linkEditor-addbutton {
+    width: 18px;
+    height: 18px;
     padding: 0;
     // font-size: 12px;
     border-radius: 10px;
@@ -34,6 +33,9 @@
         background-color: gray;
     }
 }
+.linkEditor-addbutton{
+    margin-left: 0px;
+}
 
 .linkEditor-groupsLabel {
     display: flex;
@@ -49,10 +51,11 @@
     .linkEditor-group-row {
         display: flex;
         margin-bottom: 3px;
+    }
 
-        .linkEditor-group-row-label {
-            margin-right: 6px;
-        }
+    .linkEditor-group-row-label {
+        margin-right: 6px;
+        display:inline-block;
     }
 
     .linkEditor-metadata-row {
@@ -118,7 +121,6 @@
 .linkEditor-typeButton {
     background-color: transparent;
     color: $dark-color;
-    width: 100%;
     height: 20px;
     padding: 0 3px;
     padding-bottom: 2px;
@@ -127,6 +129,8 @@
     letter-spacing: normal;
     font-size: 12px;
     font-weight: bold;
+    display: inline-block;
+    width: calc(100% - 40px);
 
     &:hover {
         background-color: $light-color;
@@ -140,6 +144,6 @@
     margin-top: 5px;
 
     .linkEditor-button {
-        margin-left: 6px;
+        margin-left: 3px;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 3c3767832..6cdd11430 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -108,7 +108,7 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
         if (this._isEditing || this._groupType === "") {
             return (
                 <div className="linkEditor-dropdown">
-                    <input type="text" value={this._groupType} placeholder="Search for or create a new group"
+                    <input type="text" value={this._groupType === "-ungrouped-" ? "" : this._groupType} placeholder="Search for or create a new group"
                         onChange={e => this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus></input>
                     <div className="linkEditor-options-wrapper">
                         {this.renderOptions()}
@@ -187,7 +187,7 @@ class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
             <div className="linkEditor-metadata-row">
                 <input className={this._keyError ? "linkEditor-error" : ""} type="text" value={this._key === "new key" ? "" : this._key} placeholder="key" onChange={e => this.setMetadataKey(e.target.value)}></input>:
                 <input type="text" value={this._value} placeholder="value" onChange={e => this.setMetadataValue(e.target.value)}></input>
-                <button onClick={() => this.removeMetadata()}><FontAwesomeIcon icon="times" size="sm" /></button>
+                <button title="remove metadata from relationship" onClick={() => this.removeMetadata()}><FontAwesomeIcon icon="times" size="sm" /></button>
             </div>
         );
     }
@@ -223,29 +223,6 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
         LinkManager.Instance.deleteGroupType(groupType);
     }
 
-    copyGroup = async (groupType: string): Promise<void> => {
-        const sourceGroupDoc = this.props.groupDoc;
-
-        const destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
-        // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc);
-        const keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
-
-
-        // create new group doc with new metadata doc
-        const destGroupDoc = new Doc();
-        destGroupDoc.title = groupType;
-        // create new metadata doc with copied kvp
-        destGroupDoc.anchor1 = sourceGroupDoc.anchor2;
-        destGroupDoc.anchor2 = sourceGroupDoc.anchor1;
-        keys.forEach(key => {
-            const val = sourceGroupDoc[key] === undefined ? "" : StrCast(sourceGroupDoc[key]);
-            destGroupDoc[key] = val;
-        });
-
-        if (destDoc) {
-            LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true);
-        }
-    }
 
     @action
     addMetadata = (groupType: string): void => {
@@ -276,53 +253,22 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
         return metadata;
     }
 
-    viewGroupAsTable = (groupType: string): JSX.Element => {
-        const keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
-        const index = keys.indexOf("");
-        if (index > -1) keys.splice(index, 1);
-        const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
-        const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
-        const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table" }));
-        const ref = React.createRef<HTMLDivElement>();
-        return <div ref={ref}><button className="linkEditor-button" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
-    }
-
     render() {
         const groupType = StrCast(this.props.groupDoc.title);
         // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
-        let buttons;
-        if (groupType === "") {
-            buttons = (
-                <>
-                    <button className="linkEditor-button" disabled={true} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
-                    <button className="linkEditor-button" disabled title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
-                    <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
-                    <button className="linkEditor-button" disabled title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
-                    <button className="linkEditor-button" disabled title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button>
-                </>
-            );
-        } else {
-            buttons = (
-                <>
-                    <button className="linkEditor-button" onClick={() => this.addMetadata(groupType)} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
-                    <button className="linkEditor-button" onClick={() => this.copyGroup(groupType)} title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
-                    <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
-                    <button className="linkEditor-button" onClick={() => this.deleteGroup(groupType)} title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
-                    {this.viewGroupAsTable(groupType)}
-                </>
-            );
-        }
+        let buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
+        let addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>;
+
         return (
             <div className="linkEditor-group">
                 <div className="linkEditor-group-row ">
-                    <p className="linkEditor-group-row-label">type:</p>
+                    {buttons}
                     <GroupTypesDropdown groupType={groupType} setGroupType={this.setGroupType} />
+                    <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove relationship from link"><FontAwesomeIcon icon="times" size="sm" /></button>
                 </div>
                 {this.renderMetadata().length > 0 ? <p className="linkEditor-group-row-label">metadata:</p> : <></>}
+                {addButton}
                 {this.renderMetadata()}
-                <div className="linkEditor-group-buttons">
-                    {buttons}
-                </div>
             </div>
         );
     }
@@ -357,9 +303,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                     <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p>
                     <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
                 </div>
-                <div className="linkEditor-groupsLabel">
-                    <b>Relationships:</b>
-                </div>
                 {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
             </div>
 
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index a8aa01306..b768eacc3 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -60,7 +60,7 @@ export class LinkMenu extends React.Component<Props> {
         if (this._editingLink === undefined) {
             return (
                 <div className="linkMenu">
-                    <button className="linkEditor-button linkEditor-clearButton" onClick={() => this.clearAllLinks()} title="Clear all links"><FontAwesomeIcon icon="trash" size="sm" /></button>
+                    {/* <button className="linkEditor-button linkEditor-clearButton" onClick={() => this.clearAllLinks()} title="Clear all links"><FontAwesomeIcon icon="trash" size="sm" /></button> */}
                     {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
                     <div className="linkMenu-list">
                         {this.renderAllGroups(groups)}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 7dd2c0fa8..5bf2e4547 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -11,6 +11,7 @@ import { ContextMenu } from '../ContextMenu';
 import { LinkFollowBox } from './LinkFollowBox';
 import './LinkMenuItem.scss';
 import React = require("react");
+import { DocumentManager } from '../../util/DocumentManager';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
 
 
@@ -30,7 +31,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     private _downY = 0;
     private _eleClone: any;
     @observable private _showMore: boolean = false;
-    @action toggleShowMore() { this._showMore = !this._showMore; }
+    @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
 
     onEdit = (e: React.PointerEvent): void => {
         e.stopPropagation();
@@ -40,17 +41,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     renderMetadata = (): JSX.Element => {
         const index = StrCast(this.props.linkDoc.title).toUpperCase() === this.props.groupType.toUpperCase() ? 0 : -1;
-        const groupDoc = index > -1 ? this.props.linkDoc : undefined;
+        const mdDoc = index > -1 ? this.props.linkDoc : undefined;
 
         let mdRows: Array<JSX.Element> = [];
-        if (groupDoc) {
-            const mdDoc = groupDoc;
-            if (mdDoc) {
-                const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
-                mdRows = keys.map(key => {
-                    return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
-                });
-            }
+        if (mdDoc) {
+            const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
+            mdRows = keys.map(key => <div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
         }
 
         return (<div className="link-metadata">{mdRows}</div>);
@@ -71,11 +67,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         document.removeEventListener("pointermove", this.onLinkButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-        if (LinkFollowBox.Instance !== undefined) {
-            LinkFollowBox.Instance.props.Document.isMinimized = false;
-            LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
-            LinkFollowBox.setAddDocTab(this.props.addDocTab);
-        }
         e.stopPropagation();
     }
 
@@ -92,26 +83,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     onContextMenu = (e: React.MouseEvent) => {
         e.preventDefault();
-        ContextMenu.Instance.addItem({ description: "Open in Link Follower", event: () => this.openLinkFollower(), icon: "link" });
         ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
         ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
     }
 
     @action.bound
     async followDefault() {
-        if (LinkFollowBox.Instance !== undefined) {
-            LinkFollowBox.setAddDocTab(this.props.addDocTab);
-            LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
-            LinkFollowBox.Instance.defaultLinkBehavior();
-        }
-    }
-
-    @action.bound
-    async openLinkFollower() {
-        if (LinkFollowBox.Instance !== undefined) {
-            LinkFollowBox.Instance.props.Document.isMinimized = false;
-            LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
-        }
+        DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
     }
 
     render() {
@@ -124,7 +102,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                     <div ref={this._drag} className="linkMenu-name" title="drag to view target. click to customize." onPointerDown={this.onLinkButtonDown}>
                         <p >{StrCast(this.props.destinationDoc.title)}</p>
                         <div className="linkMenu-item-buttons">
-                            {canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
+                            {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
                             <div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
                             <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}>
-- 
cgit v1.2.3-70-g09d2


From ee80ef34d8a9354c2bfbbf4b4c233832f2ea5e26 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 21 Feb 2020 12:09:54 -0500
Subject: made double-click link editing for link anchors.

---
 src/client/documents/Documents.ts                  |  2 +-
 .../CollectionFreeFormLinkView.tsx                 |  2 +-
 src/client/views/linking/LinkEditor.scss           |  1 +
 src/client/views/linking/LinkEditor.tsx            | 22 +++--
 src/client/views/nodes/DocuLinkBox.scss            | 16 ++++
 src/client/views/nodes/DocuLinkBox.tsx             | 95 ++++++++++++++++------
 6 files changed, 101 insertions(+), 37 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4bad24be9..aed14e4f6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -539,7 +539,7 @@ export namespace Docs {
             if (linkDocProto.layout_key1 === undefined) {
                 Cast(linkDocProto.proto, Doc, null)!.layout_key1 = DocuLinkBox.LayoutString("anchor1");
                 Cast(linkDocProto.proto, Doc, null)!.layout_key2 = DocuLinkBox.LayoutString("anchor2");
-                Cast(linkDocProto.proto, Doc, null)!.linkBoxExcludedKeys = new List(["treeViewExpandedView", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "proto", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
+                Cast(linkDocProto.proto, Doc, null)!.linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "proto", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]);
                 Cast(linkDocProto.proto, Doc, null)!.layoutKey = undefined;
             }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f0c706c1e..1038347d4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -95,7 +95,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const pt2 = [bpt.point.x, bpt.point.y];
         const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
         const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
-        const text = StrCast(this.props.A.props.Document.title);
+        const text = StrCast(this.props.A.props.Document.linkRelationship);
         return !aActive && !bActive ? (null) : (<>
             <text x={(pt1[0] + pt2[0]) / 2} y={(pt1[1] + pt2[1]) / 2}>
                 {text !== "-ungrouped-" ? text : ""}
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index 0807216c9..b47c8976e 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -4,6 +4,7 @@
     width: 100%;
     height: auto;
     font-size: 12px; // TODO
+    user-select: none;
 }
 
 .linkEditor-back {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 913a04d66..ac4f8a3cf 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,17 +1,14 @@
-import { observable, computed, action, trace } from "mobx";
-import React = require("react");
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import './LinkEditor.scss';
-import { StrCast, Cast, FieldValue } from "../../../new_fields/Types";
 import { Doc } from "../../../new_fields/Doc";
-import { LinkManager } from "../../util/LinkManager";
-import { Docs } from "../../documents/Documents";
+import { StrCast } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
-import { faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus } from '@fortawesome/free-solid-svg-icons';
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { SetupDrag } from "../../util/DragManager";
-import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField";
+import { LinkManager } from "../../util/LinkManager";
+import './LinkEditor.scss';
+import React = require("react");
 
 library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
 
@@ -279,6 +276,7 @@ interface LinkEditorProps {
     sourceDoc: Doc;
     linkDoc: Doc;
     showLinks: () => void;
+    hideback?: boolean;
 }
 @observer
 export class LinkEditor extends React.Component<LinkEditorProps> {
@@ -298,7 +296,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
 
         return !destination ? (null) : (
             <div className="linkEditor">
-                <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>
+                {this.props.hideback ? (null) : <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
                 <div className="linkEditor-info">
                     <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p>
                     <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
diff --git a/src/client/views/nodes/DocuLinkBox.scss b/src/client/views/nodes/DocuLinkBox.scss
index 57c1a66e0..7b91b4f36 100644
--- a/src/client/views/nodes/DocuLinkBox.scss
+++ b/src/client/views/nodes/DocuLinkBox.scss
@@ -5,4 +5,20 @@
     height: 25px;
     border-radius: 20px;
     pointer-events: all;
+    user-select: none;
+
+    .docuLinkBox-linkCloser {
+        position: absolute;
+        width: 18;
+        height: 18;
+        background: rgb(219, 21, 21);
+        top: -1px;
+        left: -1px;
+        border-radius: 5px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        padding-left: 2px;
+        padding-top: 1px;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index c4d99456d..42e1c9b07 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -13,6 +13,12 @@ import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ContextMenu } from "../ContextMenu";
+import { LinkEditor } from "../linking/LinkEditor";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { SelectionManager } from "../../util/SelectionManager";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
 
 type DocLinkSchema = makeInterface<[typeof documentSchema]>;
 const DocLinkDocument = makeInterface(documentSchema);
@@ -20,16 +26,22 @@ const DocLinkDocument = makeInterface(documentSchema);
 @observer
 export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(DocLinkDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocuLinkBox, fieldKey); }
-    _downx = 0;
-    _downy = 0;
+    _doubleTap = false;
+    _lastTap: number = 0;
+    _ref = React.createRef<HTMLDivElement>();
+    _downX = 0;
+    _downY = 0;
+    _isOpen = false;
+    _timeout: NodeJS.Timeout | undefined;
     @observable _x = 0;
     @observable _y = 0;
     @observable _selected = false;
-    _ref = React.createRef<HTMLDivElement>();
+    @observable _editing = false;
+    @observable _forceOpen = false;
 
     onPointerDown = (e: React.PointerEvent) => {
-        this._downx = e.clientX;
-        this._downy = e.clientY;
+        this._downX = e.clientX;
+        this._downY = e.clientY;
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.addEventListener("pointermove", this.onPointerMove);
@@ -38,11 +50,11 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     }
     onPointerMove = action((e: PointerEvent) => {
         const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
-        if (cdiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) {
+        if (!this._isOpen && cdiv && (Math.abs(e.clientX - this._downX) > 5 || Math.abs(e.clientY - this._downY) > 5)) {
             const bounds = cdiv.getBoundingClientRect();
             const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
             const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
-            const dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy));
+            const dragdist = Math.sqrt((pt[0] - this._downX) * (pt[0] - this._downX) + (pt[1] - this._downY) * (pt[1] - this._downY));
             if (separation > 100) {
                 DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack.
                 document.removeEventListener("pointermove", this.onPointerMove);
@@ -56,31 +68,53 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     onPointerUp = (e: PointerEvent) => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) {
+        if (Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 && (e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) {
             this.props.select(false);
         }
+        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+        this._lastTap = Date.now();
     }
+
+    @action
     onClick = (e: React.MouseEvent) => {
-        if (!this.props.Document.onClick) {
-            if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
-                if (this.props.Document.linkTarget === "doc") {
-                    const alias = Doc.MakeAlias(this.props.Document);
-                    alias.isButton = undefined;
-                    alias.isBackground = undefined;
-                    alias.layoutKey = "layout";
-                    this.props.addDocTab(alias, StrCast(this.props.Document.linkOpenLocation, "inTab"));
-                } else {
-                    DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
-                }
+        if (!this._doubleTap) {
+            this._editing = true;
+            if (!this.props.Document.onClick && !this._isOpen) {
+                this._timeout = setTimeout(action(() => {
+                    if (Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
+                        DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
+                    }
+                    this._editing = false;
+                }), 300 - (Date.now() - this._lastTap));
             }
-            e.stopPropagation();
+        } else {
+            this._timeout && clearTimeout(this._timeout);
+            this._timeout = undefined;
         }
+        e.stopPropagation();
+    }
+
+    openLinkDocOnRight = (e: React.MouseEvent) => {
+        this.props.addDocTab(this.props.Document, "onRight");
+    }
+    openLinkTargetOnRight = (e: React.MouseEvent) => {
+        const alias = Doc.MakeAlias(Cast(this.props.Document[this.props.fieldKey], Doc, null));
+        alias.isButton = undefined;
+        alias.isBackground = undefined;
+        alias.layoutKey = "layout";
+        this.props.addDocTab(alias, "onRight");
     }
+    @action
+    openLinkEditor = action((e: React.MouseEvent) => {
+        SelectionManager.DeselectAll();
+        this._editing = this._forceOpen = true;
+    })
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
-        funcs.push({ description: "Open Target " + (this.props.Document.linkOpenLocation !== "onRight" ? "on Right" : "in Tab"), event: () => { e.stopPropagation(); this.props.Document.linkOpenLocation = this.props.Document.linkOpenLocation !== "onRight" ? "onRight" : "inTab" }, icon: "eye" });
-        funcs.push({ description: this.props.Document.linkTarget === "doc" ? "Open Link Target" : "Open Link Doc", event: () => { e.stopPropagation(); this.props.Document.linkTarget = this.props.Document.linkTarget === "doc" ? "anchor" : "doc" }, icon: "eye" });
+        funcs.push({ description: "Open Link Target on Right", event: () => this.openLinkTargetOnRight(e), icon: "eye" });
+        funcs.push({ description: "Open Link on Right", event: () => this.openLinkDocOnRight(e), icon: "eye" });
+        funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
 
         ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
     }
@@ -94,10 +128,25 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
 
         const timecode = this.props.Document[anchor + "Timecode"];
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
+        const flyout = (
+            <div className="docuLinkBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
+                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)!} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
+                {!this._forceOpen ? (null) : <div className="docuLinkBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
+                    <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
+                </div>}
+            </div>
+        );
         return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref} style={{
                 background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
                 transform: `scale(${anchorScale / this.props.ContentScaling()})`
-            }} />;
+            }} >
+            {!this._editing && !this._forceOpen ? (null) :
+                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => this._isOpen = true} onClose={action(() => this._isOpen = this._forceOpen = this._editing = false)}>
+                    <span className="parentDocumentSelector-button" >
+                        <FontAwesomeIcon icon={"eye"} size={"lg"} />
+                    </span>
+                </Flyout>}
+        </div>;
     }
 }
-- 
cgit v1.2.3-70-g09d2

