aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/linking
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/linking')
-rw-r--r--src/client/views/linking/LinkEditor.tsx264
-rw-r--r--src/client/views/linking/LinkMenu.tsx29
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx54
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx40
4 files changed, 27 insertions, 360 deletions
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 3713a1026..435b9d904 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -10,264 +10,6 @@ import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
import React = require("react");
-interface GroupTypesDropdownProps {
- groupType: string;
- setGroupType: (group: string) => void;
-}
-// this dropdown could be generalized
-@observer
-class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
- @observable private _searchTerm: string = this.props.groupType;
- @observable private _groupType: string = this.props.groupType;
- @observable private _isEditing: boolean = false;
-
- @action
- createGroup = (groupType: string): void => {
- this.props.setGroupType(groupType);
- LinkManager.Instance.addGroupType(groupType);
- }
-
- @action
- onChange = (val: string): void => {
- this._searchTerm = val;
- this._groupType = val;
- this._isEditing = true;
- }
-
- @action
- onKeyDown = (e: React.KeyboardEvent): void => {
- if (e.key === "Enter") {
- const allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
- const groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase());
-
- if (exactFound > -1) {
- const groupType = groupOptions[exactFound];
- this.props.setGroupType(groupType);
- this._groupType = groupType;
- } else {
- this.createGroup(this._searchTerm);
- this._groupType = this._searchTerm;
- }
-
- this._searchTerm = this._groupType;
- this._isEditing = false;
- }
- }
-
- @action
- onOptionClick = (value: string, createNew: boolean): void => {
- if (createNew) {
- this.createGroup(this._searchTerm);
- this._groupType = this._searchTerm;
-
- } else {
- this.props.setGroupType(value);
- this._groupType = value;
-
- }
- this._searchTerm = this._groupType;
- this._isEditing = false;
- }
-
- @action
- onButtonPointerDown = (): void => {
- this._isEditing = true;
- }
-
- renderOptions = (): JSX.Element[] | JSX.Element => {
- if (this._searchTerm === "") return <></>;
-
- const allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
- const groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
-
- const options = groupOptions.map(groupType => {
- const ref = React.createRef<HTMLDivElement>();
- return <div key={groupType} ref={ref} className="linkEditor-option"
- onClick={() => this.onOptionClick(groupType, false)}>{groupType}</div>;
- });
-
- // if search term does not already exist as a group type, give option to create new group type
- if (!exactFound && this._searchTerm !== "") {
- const ref = React.createRef<HTMLDivElement>();
- options.push(<div key={""} ref={ref} className="linkEditor-option"
- onClick={() => this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship</div>);
- }
-
- return options;
- }
-
- render() {
- if (this._isEditing || this._groupType === "") {
- return (
- <div className="linkEditor-dropdown">
- <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()}
- </div>
- </div >
- );
- } else {
- return <button className="linkEditor-typeButton" onClick={() => this.onButtonPointerDown()}>{this._groupType}</button>;
- }
- }
-}
-
-
-interface LinkMetadataEditorProps {
- id: string;
- groupType: string;
- mdDoc: Doc;
- mdKey: string;
- mdValue: string;
- changeMdIdKey: (id: string, newKey: string) => void;
-}
-@observer
-class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
- @observable private _key: string = this.props.mdKey;
- @observable private _value: string = this.props.mdValue;
- @observable private _keyError: boolean = false;
-
- @action
- setMetadataKey = (value: string): void => {
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
-
- // don't allow user to create existing key
- const newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase());
- if (newIndex > -1) {
- this._keyError = true;
- this._key = value;
- return;
- } else {
- this._keyError = false;
- }
-
- // set new value for key
- const currIndex = groupMdKeys.findIndex(key => {
- return StrCast(key).toUpperCase() === this._key.toUpperCase();
- });
- if (currIndex === -1) console.error("LinkMetadataEditor: key was not found");
- groupMdKeys[currIndex] = value;
-
- this.props.changeMdIdKey(this.props.id, value);
- this._key = value;
- LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, [...groupMdKeys]);
- }
-
- @action
- setMetadataValue = (value: string): void => {
- if (!this._keyError) {
- this._value = value;
- Doc.GetProto(this.props.mdDoc)[this._key] = value;
- }
- }
-
- @action
- removeMetadata = (): void => {
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
-
- const index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase());
- if (index === -1) console.error("LinkMetadataEditor: key was not found");
- groupMdKeys.splice(index, 1);
-
- LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys);
- this._key = "";
- }
-
- render() {
- return (
- <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 title="remove metadata from relationship" onClick={() => this.removeMetadata()}><FontAwesomeIcon icon="times" size="sm" /></button>
- </div>
- );
- }
-}
-
-interface LinkGroupEditorProps {
- sourceDoc: Doc;
- linkDoc: Doc;
- groupDoc: Doc;
-}
-@observer
-export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
-
- private _metadataIds: Map<string, string> = new Map();
-
- constructor(props: LinkGroupEditorProps) {
- super(props);
-
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.linkRelationship));
- groupMdKeys.forEach(key => this._metadataIds.set(key, Utils.GenerateGuid()));
- }
-
- @action
- setGroupType = (groupType: string): void => {
- Doc.GetProto(this.props.groupDoc).linkRelationship = groupType;
- }
-
- removeGroupFromLink = (groupType: string): void => {
- LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType);
- }
-
- deleteGroup = (groupType: string): void => {
- LinkManager.Instance.deleteGroupType(groupType);
- }
-
-
- @action
- addMetadata = (groupType: string): void => {
- this._metadataIds.set("new key", Utils.GenerateGuid());
- const mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
- // only add "new key" if there is no other key with value "new key"; prevents spamming
- if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key");
- LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys);
- }
-
- // for key rendering purposes
- changeMdIdKey = (id: string, newKey: string) => {
- this._metadataIds.set(newKey, id);
- }
-
- renderMetadata = (): JSX.Element[] => {
- const metadata: Array<JSX.Element> = [];
- const groupDoc = this.props.groupDoc;
- const groupType = StrCast(groupDoc.linkRelationship);
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
-
- groupMdKeys.forEach((key) => {
- const val = StrCast(groupDoc[key]);
- metadata.push(
- <LinkMetadataEditor key={"mded-" + this._metadataIds.get(key)} id={this._metadataIds.get(key)!} groupType={groupType} mdDoc={groupDoc} mdKey={key} mdValue={val} changeMdIdKey={this.changeMdIdKey} />
- );
- });
- return metadata;
- }
-
- render() {
- const groupType = StrCast(this.props.groupDoc.linkRelationship);
- // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
- const buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
- const 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 ">
- {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>
- );
- }
-}
-
interface LinkEditorProps {
sourceDoc: Doc;
@@ -422,10 +164,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
render() {
const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
- const groups = [this.props.linkDoc].map(groupDoc => {
- return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc}
- sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
- });
return !destination ? (null) : (
<div className="linkEditor">
@@ -450,8 +188,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div>{this.editDescription}</div>
<div>{this.followingDropdown}</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 3b4912d3d..54b597f59 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -59,24 +59,17 @@ export class LinkMenu extends React.Component<Props> {
}
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
- const linkItems: Array<JSX.Element> = [];
- groups.forEach((group, groupType) => {
- linkItems.push(
- <LinkMenuGroup
- key={groupType}
- docView={this.props.docView}
- sourceDoc={this.props.docView.props.Document}
- group={group}
- groupType={groupType}
- showEditor={action((linkDoc: Doc) => this._editingLink = linkDoc)}
- addDocTab={this.props.addDocTab} />
- );
- });
-
- // if source doc has no links push message
- if (linkItems.length === 0) linkItems.push(<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>);
-
- return linkItems;
+ const linkItems = Array.from(groups.entries()).map(group =>
+ <LinkMenuGroup
+ key={group[0]}
+ docView={this.props.docView}
+ sourceDoc={this.props.docView.props.Document}
+ group={group[1]}
+ groupType={group[0]}
+ showEditor={action(linkDoc => this._editingLink = linkDoc)}
+ addDocTab={this.props.addDocTab} />);
+
+ return linkItems.length ? linkItems : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
}
@computed
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 29e1d921c..e53655fd3 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,17 +1,12 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { Docs } from "../../documents/Documents";
-import { DragManager, SetupDrag } from "../../util/DragManager";
+import { Cast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
import { DocumentView } from "../nodes/DocumentView";
import './LinkMenu.scss';
-import { LinkMenuItem, StartLinkTargetsDrag } from "./LinkMenuItem";
+import { LinkMenuItem } from "./LinkMenuItem";
import React = require("react");
-import { Cast } from "../../../fields/Types";
interface LinkMenuGroupProps {
sourceDoc: Doc;
@@ -26,45 +21,8 @@ interface LinkMenuGroupProps {
export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _drag = React.createRef<HTMLDivElement>();
- private _table = React.createRef<HTMLDivElement>();
private _menuRef = React.createRef<HTMLDivElement>();
- onLinkButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.addEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- document.addEventListener("pointerup", this.onLinkButtonUp);
- }
-
- onLinkButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- e.stopPropagation();
- }
-
- onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._drag.current && (e.movementX > 1 || e.movementY > 1)) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
-
- const targets = this.props.group.map(l => LinkManager.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
- StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets);
- }
- e.stopPropagation();
- }
-
- 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", childDropAction: "alias" }));
- const ref = React.createRef<HTMLDivElement>();
- return <div ref={ref}><button className="linkEditor-button linkEditor-tableButton" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
- }
-
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -86,11 +44,9 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
- {/* <div className="linkMenu-group-name">
- <p ref={this._drag} onPointerDown={this.onLinkButtonDown}
- className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
- {this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)}
- </div> */}
+ <div className="linkMenu-group-name">
+ <p ref={this._drag} className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
+ </div>
<div className="linkMenu-group-wrapper">
{groupItems}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d3fbf39a2..5c9123876 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -90,19 +90,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return true;
}
- renderMetadata = (): JSX.Element => {
- const index = StrCast(this.props.linkDoc.title).toUpperCase() === this.props.groupType.toUpperCase() ? 0 : -1;
- const mdDoc = index > -1 ? this.props.linkDoc : undefined;
-
- let mdRows: Array<JSX.Element> = [];
- 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>);
- }
-
@action
onLinkButtonDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
@@ -122,6 +109,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
onLinkButtonUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
+ DocumentView.followLinkClick(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false, false);
e.stopPropagation();
}
@@ -146,14 +134,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
}
- @action.bound
+
+ @action
public static followDefault(linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc, addDocTab: (doc: Doc, where: string) => void) {
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
if (linkDoc.followLinkLocation === "openExternal" && destinationDoc.type === DocumentType.WEB) {
window.open(`${StrCast(linkDoc.annotationUri)}#annotations:${StrCast(linkDoc.annotationId)}`, '_blank');
- return;
}
if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "default") {
@@ -166,7 +154,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
});
}
} else {
- DocumentManager.Instance.FollowLink(linkDoc, sourceDoc, doc => addDocTab(doc, "add:right"), false);
+ DocumentManager.Instance.FollowLink(linkDoc, sourceDoc, addDocTab, false);
}
linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(linkDoc.annotationId), destinationDoc);
@@ -191,8 +179,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
render() {
- const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
- const canExpand = keys ? keys.length > 0 : false;
const eyeIcon = this.props.linkDoc.hidden ? "eye-slash" : "eye";
@@ -230,7 +216,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return (
<div className="linkMenu-item">
- <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
+ <div className={"linkMenu-item-content expand-two"}>
<div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
@@ -243,22 +229,19 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-text">
- {source ? <p className="linkMenu-source-title">
- <b>Source: {source}</b></p> : null}
+ {source ? <p className="linkMenu-source-title"> <b>Source: {source}</b></p> : null}
<div className="linkMenu-title-wrapper">
<div className="destination-icon-wrapper" >
- <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /></div>
- <p className="linkMenu-destination-title"
- onPointerDown={() => LinkMenuItem.followDefault(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc, this.props.addDocTab)}>
+ <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
+ </div>
+ <p className="linkMenu-destination-title">
{this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? "Annotation in" : ""} {title}
</p>
</div>
- {this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
- {StrCast(this.props.linkDoc.description)}</p> : null} </div>
+ {!this.props.linkDoc.description ? (null) : <p className="linkMenu-description">{StrCast(this.props.linkDoc.description)}</p>}
+ </div>
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
- {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> : <></>}
<Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show link" : "Hide link"}</div></>}>
<div className="button" ref={this._editRef} onPointerDown={this.showLink}>
@@ -277,7 +260,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
</div>
</div>
- {this._showMore ? this.renderMetadata() : <></>}
</div>
</div >