diff options
Diffstat (limited to 'src/client/views/nodes/LinkBox.tsx')
-rw-r--r-- | src/client/views/nodes/LinkBox.tsx | 99 |
1 files changed, 66 insertions, 33 deletions
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 6e4d0e92a..3a2509c3d 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -2,18 +2,20 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import Xarrow from 'react-xarrows'; +import { FieldResult } from '../../../fields/Doc'; import { DocCss, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; -import { LinkManager } from '../../util/LinkManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { StyleProp } from '../StyleProvider'; import { ComparisonBox } from './ComparisonBox'; +import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkBox.scss'; @@ -22,8 +24,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string = 'link') { return FieldView.LayoutString(LinkBox, fieldKey); } - disposer: IReactionDisposer | undefined; - @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor + _disposers: { [name: string]: IReactionDisposer } = {}; + @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor constructor(props: FieldViewProps) { @@ -38,45 +40,48 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch; return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement()); }; + _hackToSeeIfDeleted: any; componentWillUnmount() { - this.disposer?.(); + this._hackToSeeIfDeleted && clearTimeout(this._hackToSeeIfDeleted); + Object.keys(this._disposers).forEach(key => this._disposers[key]()); } componentDidMount() { this._props.setContentViewBox?.(this); - this.disposer = reaction( - () => ({ drag: SnappingManager.IsDragging }), - ({ drag }) => { - !LightboxView.Contains(this.DocumentView?.()) && - setTimeout( - // need to wait for drag manager to set 'hidden' flag on dragged DOM elements - action(() => { - const a = this.anchor1, - b = this.anchor2; - let a1 = a && document.getElementById(a.Guid); - let a2 = b && document.getElementById(b.Guid); - // test whether the anchors themselves are hidden,... - if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; - else { - // .. or whether and of their DOM parents are hidden - for (; a1 && !a1.hidden; a1 = a1.parentElement); - for (; a2 && !a2.hidden; a2 = a2.parentElement); - this._hide = a1 || a2 ? true : false; - } - }) - ); - }, - { fireImmediately: true } + this._disposers.deleting = reaction( + () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())), + empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() => + (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document) + )), 1000) // prettier-ignore + ); + this._disposers.dragging = reaction( + () => SnappingManager.IsDragging, + () => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements + const a = this.anchor1, + b = this.anchor2; + let a1 = a && document.getElementById(a.ViewGuid); + let a2 = b && document.getElementById(b.ViewGuid); + // test whether the anchors themselves are hidden,... + if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; + else { + // .. or whether any of their DOM parents are hidden + for (; a1 && !a1.hidden; a1 = a1.parentElement); + for (; a2 && !a2.hidden; a2 = a2.parentElement); + this._hide = a1 || a2 ? true : false; + } + })) // prettier-ignore ); } render() { + TraceMobx(); + if (this._hide) return null; const a = this.anchor1; const b = this.anchor2; this._forceAnimate; const docView = this._props.docViewPath().lastElement(); - if (a && b && !LightboxView.Contains(docView)) { + if (a && b) { // text selection bounds are not directly observable, so we have to // force an update when anything that could affect them changes (text edits causing reflow, scrolling) a.Document[a.LayoutFieldKey]; @@ -92,18 +97,39 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation) + var foundParent = false; + const getAnchor = (field: FieldResult): Element[] => { + const docField = DocCast(field); + const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField; + const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id])); + if (ele?.className === 'linkBox-label') foundParent = true; + if (ele?.getBoundingClientRect().width) return [ele]; + const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width); + const annoOn = DocCast(doc.annotationOn); + if (eles.length || !annoOn) return eles; + const pareles = getAnchor(annoOn); + foundParent = pareles.length ? true : false; + return pareles; + }; // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>), // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right // otherwise, we just use the computed nearest point on the document boundary to the target Document - const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement(); - const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement(); + const targetAhyperlinks = getAnchor(this.dataDoc.link_anchor_1); + const targetBhyperlinks = getAnchor(this.dataDoc.link_anchor_2); - const aid = targetAhyperlink?.id || a.Document[Id]; - const bid = targetBhyperlink?.id || b.Document[Id]; - if (!document.getElementById(aid) || !document.getElementById(bid)) { + const container = this.DocumentView?.().containerViewPath?.().lastElement()?.ContentDiv; + const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id; + const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id; + if (!aid || !bid) { setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); return null; } + if (foundParent) { + setTimeout( + action(() => (this._forceAnimate = this._forceAnimate + 0.01)), + 1 + ); + } if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting); @@ -149,6 +175,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { color={color} labels={ <div + id={this.DocumentView?.().DocUniqueId} + className={'linkBox-label'} style={{ borderRadius: '8px', pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined, @@ -192,6 +220,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { </> ); } + + setTimeout( + action(() => (this._forceAnimate = this._forceAnimate + 1)), + 2 + ); return ( <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}> <ComparisonBox |