aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/linking
diff options
context:
space:
mode:
authorgeireann <geireann.lindfield@gmail.com>2021-10-14 14:30:47 -0400
committergeireann <geireann.lindfield@gmail.com>2021-10-14 14:30:47 -0400
commit0f83dfd2aa9738509fdb09cd54005d1ccf5c552c (patch)
treea9032715c1ca3b9eb3dadbb17776ed158c30b4a5 /src/client/views/linking
parent53019659c2335906ac9e42d755548ea35dfc0365 (diff)
parent662176f25e25d3bf31cfb8ec6e3792d18f77f37d (diff)
Merge branch 'master' into splash-screen-3-Anh-En-Hua
Diffstat (limited to 'src/client/views/linking')
-rw-r--r--src/client/views/linking/LinkEditor.scss18
-rw-r--r--src/client/views/linking/LinkEditor.tsx116
-rw-r--r--src/client/views/linking/LinkMenu.tsx2
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx20
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx22
-rw-r--r--src/client/views/linking/LinkPopup.scss2
-rw-r--r--src/client/views/linking/LinkPopup.tsx44
-rw-r--r--src/client/views/linking/LinkRelationshipSearch.tsx63
8 files changed, 231 insertions, 56 deletions
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index e45a91d57..abd413f57 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -106,6 +106,24 @@
}
}
+.linkEditor-relationship-dropdown {
+ position: absolute;
+ width: 154px;
+ max-height: 90px;
+ overflow: auto;
+ background: white;
+
+ p {
+ padding: 3px;
+ cursor: pointer;
+ border: 1px solid $medium-gray;
+ }
+
+ p:hover {
+ background: $light-blue;
+ }
+}
+
.linkEditor-followingDropdown {
padding-left: 26px;
padding-right: 6.5px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 9d0938a6e..5b5c3cd01 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -2,12 +2,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { DateCast, StrCast } from "../../../fields/Types";
+import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc";
+import { DateCast, StrCast, Cast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
+import { LinkRelationshipSearch } from "./LinkRelationshipSearch";
import React = require("react");
+import { ToString } from "../../../fields/FieldSymbols";
interface LinkEditorProps {
@@ -19,13 +21,15 @@ interface LinkEditorProps {
@observer
export class LinkEditor extends React.Component<LinkEditorProps> {
- @observable description = StrCast(LinkManager.currentLink?.description);
+ @observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
@observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
@observable openDropdown: boolean = false;
@observable showInfo: boolean = false;
@computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
@observable private buttonColor: string = "";
@observable private relationshipButtonColor: string = "";
+ @observable private relationshipSearchVisibility: string = "none";
+ @observable private searchIsActive: boolean = false;
//@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
@@ -38,24 +42,74 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@undoBatch
setRelationshipValue = action((value: string) => {
if (LinkManager.currentLink) {
+ const prevRelationship = LinkManager.currentLink.linkRelationship as string;
LinkManager.currentLink.linkRelationship = value;
+ Doc.GetProto(LinkManager.currentLink).linkRelationship = value;
+ const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
+ const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes);
+ const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+
+ // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
+ if (!linkRelationshipList?.includes(value)) {
+ linkRelationshipList.push(value);
+ linkRelationshipSizes.push(1);
+ const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")";
+ linkColorList.push(randColor)
+ // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
+ } else if (linkRelationshipList && value != prevRelationship) {
+ const index = linkRelationshipList.indexOf(value);
+ //increment size of new relationship size
+ if (index !== -1 && index < linkRelationshipSizes.length) {
+ const pvalue = linkRelationshipSizes[index];
+ linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1);
+ }
+ //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
+ if (linkRelationshipList.includes(prevRelationship)) {
+ const pindex = linkRelationshipList.indexOf(prevRelationship);
+ if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
+ const pvalue = linkRelationshipSizes[pindex];
+ linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1));
+ }
+ }
+
+ }
this.relationshipButtonColor = "rgb(62, 133, 55)";
setTimeout(action(() => this.relationshipButtonColor = ""), 750);
return true;
}
});
+ /**
+ * returns list of strings with possible existing relationships that contain what is currently in the input field
+ */
+ @action
+ getRelationshipResults = () => {
+ const query = this.relationship; //current content in input box
+ const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
+ if (linkRelationshipList) {
+ return linkRelationshipList.filter(rel => rel.includes(query));
+ }
+ }
+
+ /**
+ * toggles visibility of the relationship search results when the input field is focused on
+ */
+ @action
+ toggleRelationshipResults = () => {
+ this.relationshipSearchVisibility = this.relationshipSearchVisibility === "none" ? "block" : "none";
+ }
+
@undoBatch
setDescripValue = action((value: string) => {
if (LinkManager.currentLink) {
- LinkManager.currentLink.description = value;
+ Doc.GetProto(LinkManager.currentLink).description = value;
this.buttonColor = "rgb(62, 133, 55)";
setTimeout(action(() => this.buttonColor = ""), 750);
return true;
}
});
- onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
this.setDescripValue(this.description);
document.getElementById('input')?.blur();
@@ -69,16 +123,38 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
}
}
- onDown = () => this.setDescripValue(this.description);
+ onDescriptionDown = () => this.setDescripValue(this.description);
onRelationshipDown = () => this.setRelationshipValue(this.relationship);
+ onBlur = () => {
+ //only hide the search results if the user clicks out of the input AND not on any of the search results
+ // i.e. if search is not active
+ if (!this.searchIsActive) {
+ this.toggleRelationshipResults();
+ }
+ }
+ onFocus = () => {
+ this.toggleRelationshipResults();
+ }
+ toggleSearchIsActive = () => {
+ this.searchIsActive = !this.searchIsActive;
+ }
+
@action
- handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
+ handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
@action
- handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; }
-
+ handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.relationship = e.target.value;
+ }
+ @action
+ handleRelationshipSearchChange = (result: string) => {
+ this.setRelationshipValue(result);
+ this.toggleRelationshipResults();
+ this.relationship = result;
+ }
@computed
get editRelationship() {
+ //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
return <div className="linkEditor-description">
<div className="linkEditor-description-label">Link Relationship:</div>
<div className="linkEditor-description-input">
@@ -87,11 +163,19 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
style={{ width: "100%" }}
id="input"
value={this.relationship}
- placeholder={"enter link label"}
- // color={"rgb(88, 88, 88)"}
+ autoComplete={"off"}
+ placeholder={"Enter link relationship"}
onKeyDown={this.onRelationshipKey}
onChange={this.handleRelationshipChange}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
></input>
+ <LinkRelationshipSearch
+ results={this.getRelationshipResults()}
+ display={this.relationshipSearchVisibility}
+ handleRelationshipSearchChange={this.handleRelationshipSearchChange}
+ toggleSearch={this.toggleSearchIsActive}
+ />
</div>
<div className="linkEditor-description-add-button"
style={{ background: this.relationshipButtonColor }}
@@ -108,17 +192,17 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-description-editing">
<input
style={{ width: "100%" }}
+ autoComplete={"off"}
id="input"
value={this.description}
- placeholder={"enter link label"}
- // color={"rgb(88, 88, 88)"}
- onKeyDown={this.onKey}
- onChange={this.handleChange}
+ placeholder={"Enter link description"}
+ onKeyDown={this.onDescriptionKey}
+ onChange={this.handleDescriptionChange}
></input>
</div>
<div className="linkEditor-description-add-button"
style={{ background: this.buttonColor }}
- onPointerDown={this.onDown}>Set</div>
+ onPointerDown={this.onDescriptionDown}>Set</div>
</div>
</div>;
}
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 6fc860447..53fe3f682 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -41,7 +41,7 @@ export class LinkMenu extends React.Component<Props> {
/**
* maps each link to a JSX element to be rendered
- * @param groups LinkManager containing info of all of the links
+ * @param groups containing info of all of the links
* @returns list of link JSX elements if there at least one linked element
*/
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index c7586a467..03377ad4e 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
+import { Doc, StrListCast } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { Cast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
@@ -20,6 +20,22 @@ interface LinkMenuGroupProps {
export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _menuRef = React.createRef<HTMLDivElement>();
+ getBackgroundColor = (): string => {
+ const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
+ const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+ let color = "white";
+ // if this link's relationship property is not default "link", set its color
+ if (linkRelationshipList) {
+ const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
+ const RGBcolor: string = linkColorList[relationshipIndex];
+ if (RGBcolor) {
+ //set opacity to 0.25 by modifiying the rgb string
+ color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)";
+ }
+ }
+ return color;
+ }
+
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -39,7 +55,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
- <div className="linkMenu-group-name">
+ <div className="linkMenu-group-name" style={{ background: this.getBackgroundColor() }}>
<p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p>
</div>
<div className="linkMenu-group-wrapper">
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 53a7ae9ab..96cc6d600 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -79,7 +79,9 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
onEdit = (e: React.PointerEvent): void => {
LinkManager.currentLink = this.props.linkDoc;
setupMoveUpEvents(this, e, e => {
- DragManager.StartDocumentDrag([this._editRef.current!], new DragManager.DocumentDragData([this.props.linkDoc]), e.x, e.y);
+ const dragData = new DragManager.DocumentDragData([this.props.linkDoc], "alias");
+ dragData.removeDropProperties = ["hidden"];
+ DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
return true;
}, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
}
@@ -163,19 +165,19 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
- <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.hidden ? "eye-slash" : "eye"} size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}</div></>}>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={"eye"} size="sm" /></div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={!this.props.linkDoc.linkDisplay ? "eye-slash" : "eye"} size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}</div></>}>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : this.props.linkDoc.linkDisplay ? "#4476f7"/* $medium-blue */ : "" }} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={"project-diagram"} size="sm" /></div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.linkAutoMove ? "play" : "pause"} size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}</div></>}>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : !this.props.linkDoc.linkAutoMove ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={"play"} size="sm" /></div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
index 8ae65158d..60c9ebfcd 100644
--- a/src/client/views/linking/LinkPopup.scss
+++ b/src/client/views/linking/LinkPopup.scss
@@ -5,7 +5,7 @@
height: 200px;
width: 200px;
position: absolute;
- padding: 15px;
+ // padding: 15px;
border-radius: 3px;
input {
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index df469c53b..c8be9069c 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,33 +1,21 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, observable, runInAction } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, DocListCast } from '../../../fields/Doc';
-import { Cast, StrCast } from '../../../fields/Types';
-import { WebField } from '../../../fields/URLField';
-import { emptyFunction, setupMoveUpEvents, returnFalse, returnTrue, returnEmptyDoclist, returnEmptyFilter } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager } from '../../util/DragManager';
-import { Hypothesis } from '../../util/HypothesisUtils';
-import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
-import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import './LinkPopup.scss';
-import React = require("react");
+import { EditorView } from 'prosemirror-view';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DefaultStyleProvider } from '../StyleProvider';
import { Transform } from '../../util/Transform';
-import { DocUtils } from '../../documents/Documents';
-import { SearchBox } from '../search/SearchBox';
-import { EditorView } from 'prosemirror-view';
+import { undoBatch } from '../../util/UndoManager';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { SearchBox } from '../search/SearchBox';
+import { DefaultStyleProvider } from '../StyleProvider';
+import './LinkPopup.scss';
+import React = require("react");
+import { Doc, Opt } from '../../../fields/Doc';
interface LinkPopupProps {
showPopup: boolean;
+ linkFrom?: () => Doc | undefined;
// groupType: string;
// linkDoc: Doc;
// docView: DocumentView;
@@ -62,9 +50,10 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
render() {
const popupVisibility = this.props.showPopup ? "block" : "none";
+ const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined;
return (
<div className="linkPopup-container" style={{ display: popupVisibility }}>
- <div className="linkPopup-url-container">
+ {/* <div className="linkPopup-url-container">
<input autoComplete="off" type="text" value={this.linkURL} placeholder="Enter URL..." onChange={this.onLinkChange} />
<button onPointerDown={e => this.makeLinkToURL(this.linkURL, "add:right")}
style={{ display: "block", margin: "10px auto", }}>Apply hyperlink</button>
@@ -72,14 +61,17 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
<div className="divider">
<div className="line"></div>
<p className="divider-text">or</p>
- </div>
+ </div> */}
<div className="linkPopup-document-search-container">
{/* <i></i>
<input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input"
className="linkPopup-searchBox searchBox-input" /> */}
- <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
+ <SearchBox
+ Document={CurrentUserUtils.MySearchPanelDoc}
DataDoc={CurrentUserUtils.MySearchPanelDoc}
+ linkFrom={linkDoc}
+ linkSearch={true}
fieldKey="data"
dropAction="move"
isSelected={returnTrue}
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
new file mode 100644
index 000000000..53da880e4
--- /dev/null
+++ b/src/client/views/linking/LinkRelationshipSearch.tsx
@@ -0,0 +1,63 @@
+import { observer } from "mobx-react";
+import './LinkEditor.scss';
+import React = require("react");
+
+interface LinkRelationshipSearchProps {
+ results: string[] | undefined;
+ display: string;
+ //callback fn to set rel + hide dropdown upon setting
+ handleRelationshipSearchChange: (result: string) => void;
+ toggleSearch: () => void;
+}
+@observer
+export class LinkRelationshipSearch extends React.Component<LinkRelationshipSearchProps> {
+
+ handleResultClick = (e: React.MouseEvent) => {
+ const relationship = (e.target as HTMLParagraphElement).textContent;
+ if (relationship) {
+ this.props.handleRelationshipSearchChange(relationship);
+ }
+ }
+
+ handleMouseEnter = () => {
+ this.props.toggleSearch();
+ }
+
+ handleMouseLeave = () => {
+ this.props.toggleSearch();
+ }
+
+ /**
+ * Render an empty div to increase the height of LinkEditor to accommodate 2+ results
+ */
+ emptyDiv = () => {
+ if (this.props.results && this.props.results.length > 2 && this.props.display === "block") {
+ return <div style={{ height: "50px" }} />;
+ }
+ }
+
+ render() {
+ return (
+ <div className="linkEditor-relationship-dropdown-container">
+ <div className="linkEditor-relationship-dropdown"
+ style={{ display: this.props.display }}
+ onMouseEnter={this.handleMouseEnter}
+ onMouseLeave={this.handleMouseLeave}
+ >
+ { // return a dropdown of relationship results if there exist results
+ this.props.results
+ ? this.props.results.map(result => {
+ return <p key={result} onClick={this.handleResultClick}>
+ {result}
+ </p>;
+ })
+ : <p>No matching relationships</p>
+ }
+ </div>
+
+ {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */}
+ {this.emptyDiv()}
+ </div>
+ );
+ }
+} \ No newline at end of file