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.

---
 .../collectionFreeForm/CollectionFreeFormLinkView.tsx            | 9 +++++++--
 1 file changed, 7 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 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
-- 
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

