diff options
Diffstat (limited to 'src/client/views/linking')
| -rw-r--r-- | src/client/views/linking/LinkEditor.tsx | 264 | ||||
| -rw-r--r-- | src/client/views/linking/LinkMenu.tsx | 29 | ||||
| -rw-r--r-- | src/client/views/linking/LinkMenuGroup.tsx | 54 | ||||
| -rw-r--r-- | src/client/views/linking/LinkMenuItem.tsx | 40 |
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 > |
