import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { Cast, DocCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { LinkManager } from '../../util/LinkManager'; import { SnappingManager } from '../../util/SnappingManager'; import { undoable } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { LinkInfo } from '../nodes/LinkDocPreview'; import { OpenWhere } from '../nodes/OpenWhere'; import './LinkMenuItem.scss'; interface LinkMenuItemProps { groupType: string; linkDoc: Doc; docView: DocumentView; sourceDoc: Doc; destinationDoc: Doc; clearLinkEditor?: () => void; menuRef: React.Ref; itemHandler?: (doc: Doc) => void; } // drag links and drop link targets (embedding them if needed) export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) { const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => Doc.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; if (draggedDocs.length) { const moddrag: Doc[] = []; draggedDocs.forEach(async draggedDoc => { const doc = await Cast(draggedDoc.annotationOn, Doc); if (doc) moddrag.push(doc); }); const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); dragData.canEmbed = true; dragData.dropAction = dropActionType.embed; DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined); } } @observer export class LinkMenuItem extends ObservableReactComponent { private _drag = React.createRef(); _editRef = React.createRef(); constructor(props: LinkMenuItemProps) { super(props); makeObservable(this); } @observable private _showMore: boolean = false; @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; } @computed get sourceAnchor() { const ldoc = this._props.linkDoc; if (this._props.sourceDoc !== ldoc.link_anchor_1 && this._props.sourceDoc !== ldoc.link_anchor_2) { if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1); if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2); } return this._props.sourceDoc; } onIconDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { const ancestor = DocumentView.linkCommonAncestor(this._props.linkDoc); if (!ancestor?.ComponentView?.removeDocument?.(this._props.linkDoc)) { ancestor?.ComponentView?.addDocument?.(this._props.linkDoc); } }); }; onEdit = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, moveEv => { const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed); dragData.dropPropertiesToRemove = ['hidden']; DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y); return true; }, emptyFunction, action(() => { const trail = DocCast(this._props.docView.Document.presentationTrail); if (trail) { Doc.ActivePresentation = trail; DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight); } else { DocumentView.SelectView(this._props.docView, false); LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc; LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined; if ((SnappingManager.PropertiesWidth ?? 0) < 100) { setTimeout( action(() => { SnappingManager.SetPropertiesWidth(250); }) ); } } }) ); }; onLinkButtonDown = (e: React.PointerEvent): void => { setupMoveUpEvents( this, e, moveEv => { const eleClone = this._drag.current?.cloneNode(true) as HTMLElement; if (eleClone) { eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`; StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]); this._props.clearLinkEditor?.(); } return true; }, emptyFunction, () => { this._props.clearLinkEditor?.(); if (this._props.itemHandler) { this._props.itemHandler?.(this._props.linkDoc); } else { const focusDoc = Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc ? Cast(this._props.linkDoc.link_anchor_1, Doc, null) : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc ? Cast(this._props.linkDoc.link_anchor_12, Doc, null) : undefined; if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true }); DocumentView.FollowLink(this._props.linkDoc, this._props.sourceDoc, false); } } ); }; deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents( this, e, returnFalse, emptyFunction, undoable( action(() => Doc.DeleteLink?.(this._props.linkDoc)), 'delete link' ) ); @observable _hover = false; docView = () => this._props.docView; render() { const destinationIcon = Doc.toIcon(this._props.destinationDoc); const title = StrCast(this._props.destinationDoc.title).length > 18 ? StrCast(this._props.destinationDoc.title).substr(0, 14) + '...' : this._props.destinationDoc.title; const source = this._props.sourceDoc.type === DocumentType.RTF ? this._props.linkDoc.storedText ? StrCast(this._props.linkDoc.storedText).length > 17 ? StrCast(this._props.linkDoc.storedText).substr(0, 18) : this._props.linkDoc.storedText : undefined : undefined; return (
{ this._hover = true; })} onPointerLeave={action(() => { this._hover = false; })} style={{ fontSize: this._hover ? 'larger' : undefined, fontWeight: this._hover ? 'bold' : undefined, background: LinkManager.Instance.currentLink === this._props.linkDoc ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, }}>
Edit Link
}>
e.stopPropagation()}>
Show/Hide Link
}>
this._props.linkDoc && this._props.clearLinkEditor && LinkInfo.SetLinkInfo({ DocumentView: this.docView, styleProvider: this._props.docView._props.styleProvider, linkSrc: this._props.sourceDoc, linkDoc: this._props.linkDoc, showHeader: false, location: [(this._drag.current?.getBoundingClientRect().left ?? 100) + 40, (this._drag.current?.getBoundingClientRect().top ?? e.clientY) + 25], noPreview: false, }) }> {source ? (

{' '} Source: {StrCast(source)}

) : null}
Follow Link
}>

{this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}

{!this._props.linkDoc.link_description ? null :

{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}

}
Delete Link
}>
e.stopPropagation()}>
); } }