diff options
author | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
---|---|---|
committer | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
commit | f3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch) | |
tree | a7bc895266b53bb620dbd2dd71bad2e83b555446 /src/client/views/nodes/DocumentLinksButton.tsx | |
parent | b5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff) | |
parent | 136f3d9f349d54e8bdd73b6380ea47c19e5edebf (diff) |
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/nodes/DocumentLinksButton.tsx')
-rw-r--r-- | src/client/views/nodes/DocumentLinksButton.tsx | 153 |
1 files changed, 86 insertions, 67 deletions
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 4db0bf5fa..d1805308d 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -1,22 +1,23 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; +import { Tooltip } from '@mui/material'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, Opt } from '../../../fields/Doc'; +import * as React from 'react'; +import { StopEvent, emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { Doc } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; -import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; +import { UndoManager, undoBatch } from '../../util/UndoManager'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import './DocumentLinksButton.scss'; import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { TaskCompletionBox } from './TaskCompletedBox'; -import React = require('react'); -import _ = require('lodash'); import { PinProps } from './trails'; +import { DocData } from '../../../fields/DocSymbols'; interface DocumentLinksButtonProps { View: DocumentView; @@ -27,26 +28,45 @@ interface DocumentLinksButtonProps { StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed) ShowCount?: boolean; scaling?: () => number; // how uch doc is scaled so that link buttons can invert it + hideCount?: () => boolean; +} + +export class DocButtonState { + @observable public StartLink: Doc | undefined = undefined; //origin's Doc, if defined + @observable public StartLinkView: DocumentView | undefined = undefined; + @observable public AnnotationId: string | undefined = undefined; + @observable public AnnotationUri: string | undefined = undefined; + @observable public LinkEditorDocView: DocumentView | undefined = undefined; + public static _instance: DocButtonState | undefined; + public static get Instance() { + return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState()); + } + constructor() { + makeObservable(this); + } } @observer -export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> { +export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksButtonProps> { private _linkButton = React.createRef<HTMLDivElement>(); - @observable public static StartLink: Doc | undefined; //origin's Doc, if defined - @observable public static StartLinkView: DocumentView | undefined; - @observable public static AnnotationId: string | undefined; - @observable public static AnnotationUri: string | undefined; - @observable public static LinkEditorDocView: DocumentView | undefined; + public static get StartLink() { return DocButtonState.Instance.StartLink; } // prettier-ignore + public static set StartLink(value) { runInAction(() => (DocButtonState.Instance.StartLink = value)); } // prettier-ignore + @observable public static StartLinkView: DocumentView | undefined = undefined; + @observable public static AnnotationId: string | undefined = undefined; + @observable public static AnnotationUri: string | undefined = undefined; + constructor(props: any) { + super(props); + makeObservable(this); + } - @action @undoBatch onLinkButtonMoved = (e: PointerEvent) => { - if (this.props.InMenu && this.props.StartLink) { + if (this._props.InMenu && this._props.StartLink) { if (this._linkButton.current !== null) { const linkDrag = UndoManager.StartBatch('Drag Link'); - this.props.View && - DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, { + this._props.View && + DragManager.StartLinkDrag(this._linkButton.current, this._props.View, this._props.View.ComponentView?.getAnchor, e.pageX, e.pageY, { dragComplete: dropEv => { - if (this.props.View && dropEv.linkDocument) { + if (this._props.View && dropEv.linkDocument) { // dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop !dropEv.linkDocument.link_relationship && (Doc.GetProto(dropEv.linkDocument).link_relationship = 'hyperlink'); } @@ -68,11 +88,11 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { - doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc); + doubleTap && DocumentView.showBackLinks(this._props.View.Document); }), undefined, undefined, - action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View)) + action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View)) ); }; @@ -83,33 +103,32 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { - if (doubleTap && this.props.InMenu && this.props.StartLink) { - //action(() => Doc.BrushDoc(this.props.View.Document)); - if (DocumentLinksButton.StartLink === this.props.View.props.Document) { + if (doubleTap && this._props.InMenu && this._props.StartLink) { + //action(() => Doc.BrushDoc(this._props.View.Document)); + if (DocumentLinksButton.StartLink === this._props.View.Document) { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; } else { - DocumentLinksButton.StartLink = this.props.View.props.Document; - DocumentLinksButton.StartLinkView = this.props.View; + DocumentLinksButton.StartLink = this._props.View.Document; + DocumentLinksButton.StartLinkView = this._props.View; } } }) ); }; - @action @undoBatch onLinkClick = (e: React.MouseEvent): void => { - if (this.props.InMenu && this.props.StartLink) { + if (this._props.InMenu && this._props.StartLink) { DocumentLinksButton.AnnotationId = undefined; DocumentLinksButton.AnnotationUri = undefined; - if (DocumentLinksButton.StartLink === this.props.View.props.Document) { + if (DocumentLinksButton.StartLink === this._props.View.Document) { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; } else { //if this LinkButton's Document is undefined - DocumentLinksButton.StartLink = this.props.View.props.Document; - DocumentLinksButton.StartLinkView = this.props.View; + DocumentLinksButton.StartLink = this._props.View.Document; + DocumentLinksButton.StartLinkView = this._props.View; } } }; @@ -120,7 +139,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp e, returnFalse, emptyFunction, - action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)) + action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View)) ); }; @@ -132,49 +151,48 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp DocumentLinksButton.StartLinkView = undefined; DocumentLinksButton.AnnotationId = undefined; DocumentLinksButton.AnnotationUri = undefined; - //!this.props.StartLink + //!this._props.StartLink } else if (startLink !== endLink) { - endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink; - startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink; + endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink; + startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink; const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined }); LinkManager.currentLink = linkDoc; - if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { - // if linking from a Hypothes.is annotation - Doc.GetProto(linkDoc as Doc).linksToAnnotation = true; - Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId; - Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri; - const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink); - Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc - } - if (linkDoc) { + if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { + // if linking from a Hypothes.is annotation + const linkDocData = linkDoc[DocData]; + linkDocData.linksToAnnotation = true; + linkDocData.annotationId = DocumentLinksButton.AnnotationId; + linkDocData.annotationUri = DocumentLinksButton.AnnotationUri; + const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink); + Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc + } + TaskCompletionBox.textDisplayed = 'Link Created'; TaskCompletionBox.popupX = screenX; TaskCompletionBox.popupY = screenY - 133; TaskCompletionBox.taskCompleted = true; - if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) { - LinkDescriptionPopup.popupX = screenX; - LinkDescriptionPopup.popupY = screenY - 100; - LinkDescriptionPopup.descriptionPopup = true; + if (LinkDescriptionPopup.Instance.showDescriptions === 'ON' || !LinkDescriptionPopup.Instance.showDescriptions) { + LinkDescriptionPopup.Instance.popupX = screenX; + LinkDescriptionPopup.Instance.popupY = screenY - 100; + LinkDescriptionPopup.Instance.display = true; } const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.popupX + 200 > rect.width) { - LinkDescriptionPopup.popupX -= 190; + if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { + LinkDescriptionPopup.Instance.popupX -= 190; TaskCompletionBox.popupX -= 40; } - if (LinkDescriptionPopup.popupY + 100 > rect.height) { - LinkDescriptionPopup.popupY -= 40; + if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { + LinkDescriptionPopup.Instance.popupY -= 40; TaskCompletionBox.popupY -= 40; } setTimeout( - action(() => { - TaskCompletionBox.taskCompleted = false; - }), + action(() => (TaskCompletionBox.taskCompleted = false)), 2500 ); } @@ -189,8 +207,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp @computed get filteredLinks() { const results = [] as Doc[]; - const filters = this.props.View.props.childFilters(); - Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => { + const filters = this._props.View._props.childFilters(); + Array.from(new Set<Doc>(this._props.View.allLinks)).forEach(link => { if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.link_anchor_2 as Doc], filters, []).length || DocUtils.FilterDocs([link.link_anchor_1 as Doc], filters, []).length) { results.push(link); } @@ -205,8 +223,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp */ @computed get linkButtonInner() { const btnDim = 30; - const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink; - const scaling = Math.min(1, this.props.scaling?.() || 1); + const isActive = DocumentLinksButton.StartLink === this._props.View.Document && this._props.StartLink; + const scaling = Math.min(1, this._props.scaling?.() || 1); const showLinkCount = (onHover?: boolean, offset?: boolean) => ( <div className="documentLinksButton-showCount" @@ -220,16 +238,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp <span style={{ width: '100%', display: 'inline-block', textAlign: 'center' }}>{Array.from(this.filteredLinks).length}</span> </div> ); - return this.props.ShowCount ? ( - showLinkCount(this.props.OnHover, this.props.Bottom) + return this._props.ShowCount ? ( + showLinkCount(this._props.OnHover, this._props.Bottom) ) : ( <div className="documentLinksButton-menu"> - {this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again + {this._props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? StopEvent : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}> <FontAwesomeIcon className="documentdecorations-icon" icon="link" /> </div> ) : null} - {!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node + {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( //if the origin node is not this node <div className={'documentLinksButton-endLink'} ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}> <FontAwesomeIcon className="documentdecorations-icon" icon="link" /> </div> @@ -239,20 +257,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp } render() { - const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link'; + if (this.props.hideCount?.()) return null; + const menuTitle = this._props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link'; const buttonTitle = 'Tap to view links; double tap to open link collection'; - const title = this.props.ShowCount ? buttonTitle : menuTitle; + const title = this._props.ShowCount ? buttonTitle : menuTitle; //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu - return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? null : ( + return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : ( <div className="documentLinksButton-wrapper" style={{ - position: this.props.InMenu ? 'relative' : 'absolute', + position: this._props.InMenu ? 'relative' : 'absolute', top: 0, pointerEvents: 'none', }}> - {DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>} + {DocButtonState.Instance.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>} </div> ); } |