diff options
| author | bobzel <zzzman@gmail.com> | 2023-06-23 21:44:01 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-06-23 21:44:01 -0400 |
| commit | 85c017527f209c9d007d67ac70958843ab45e729 (patch) | |
| tree | e2649860002e0c60e98d84439a67235002ddd9a4 /src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | |
| parent | e9d5dbeef2bf1dab9dfb863d970b70b3074e3d0a (diff) | |
| parent | 1429ab79eac9aa316082f52c14c576f6b3a97111 (diff) | |
Merge branch 'master' into heartbeat
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | 138 |
1 files changed, 89 insertions, 49 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index bf9de6760..f1d98d22a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,9 +1,10 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Field } from '../../../../fields/Doc'; +import { DocCss } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; -import { Cast, NumCast } from '../../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; import { LinkManager } from '../../../util/LinkManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -34,17 +35,17 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo this._anchorDisposer = reaction( () => [ this.props.A.props.ScreenToLocalTransform(), - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights, + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss], this.props.B.props.ScreenToLocalTransform(), - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights, + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss], ], action(() => { this._start = Date.now(); this._timeout && clearTimeout(this._timeout); this._timeout = setTimeout(this.timeout, 25); - setTimeout(this.placeAnchors); + setTimeout(this.placeAnchors, 10); // when docs are dragged, their transforms will update before a render has been performed. placeanchors needs to come after a render to find things in the dom. a 0 timeout will still come before the render }), { fireImmediately: true } ); @@ -58,7 +59,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo 0 ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout( - action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), + action(() => (!LinkDocs.length || !linkDoc.link_displayLine) && (this._opacity = 0.05)), 750 ); // this will unhighlight the link line. const a = A.ContentDiv.getBoundingClientRect(); @@ -72,36 +73,36 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo // if there's an element in the DOM with a classname containing a link anchor's id, // 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(window.document.getElementsByClassName((linkDoc.anchor1 as Doc)[Id])).lastElement(); - const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor2 as Doc)[Id])).lastElement(); + const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_1 as Doc)[Id])).lastElement(); + const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement(); if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return; if (!targetAhyperlink) { - if (linkDoc.linkAutoMove) { - linkDoc.anchor1_x = ((apt.point.x - aleft) / awidth) * 100; - linkDoc.anchor1_y = ((apt.point.y - atop) / aheight) * 100; + if (linkDoc.link_autoMoveAnchors) { + linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100; + linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100; } } else { const m = targetAhyperlink.getBoundingClientRect(); const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); const mpx = mp[0] / A.props.PanelWidth(); const mpy = mp[1] / A.props.PanelHeight(); - if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100; - if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100; + if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100; + if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100; if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0; else linkDoc.opacity = 1; } if (!targetBhyperlink) { - if (linkDoc.linkAutoMove) { - linkDoc.anchor2_x = ((bpt.point.x - bleft) / bwidth) * 100; - linkDoc.anchor2_y = ((bpt.point.y - btop) / bheight) * 100; + if (linkDoc.link_autoMoveAnchors) { + linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100; + linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100; } } else { const m = targetBhyperlink.getBoundingClientRect(); const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); const mpx = mp[0] / B.props.PanelWidth(); const mpy = mp[1] / B.props.PanelHeight(); - if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100; - if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100; + if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100; + if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100; if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0; else linkDoc.opacity = 1; } @@ -112,17 +113,21 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo this, e, (e, down, delta) => { - this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0]; - this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1]; + this.props.LinkDocs[0].link_relationship_OffsetX = NumCast(this.props.LinkDocs[0].link_relationship_OffsetX) + delta[0]; + this.props.LinkDocs[0].link_relationship_OffsetY = NumCast(this.props.LinkDocs[0].link_relationship_OffsetY) + delta[1]; return false; }, emptyFunction, - () => { + action(() => { + SelectionManager.DeselectAll(); + SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); + LinkManager.currentLink = this.props.LinkDocs[0]; + this.toggleProperties(); // OverlayView.Instance.addElement( // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]} // showLinks={action(() => { })} // />, { x: 300, y: 300 }); - } + }) ); }; @@ -132,6 +137,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo height = rect.height; var el = el.parentNode; while (el && el !== document.body) { + if (el.className === 'tabDocView-content') break; rect = el.getBoundingClientRect?.(); if (rect?.width) { if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom; @@ -163,15 +169,16 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo @action toggleProperties = () => { - if (SettingsManager.propertiesWidth > 0) { - SettingsManager.propertiesWidth = 0; - } else { + if ((SettingsManager.propertiesWidth ?? 0) < 100) { SettingsManager.propertiesWidth = 250; } }; + @action onClickLine = () => { + SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); + LinkManager.currentLink = this.props.LinkDocs[0]; this.toggleProperties(); }; @@ -199,15 +206,17 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const bclipped = bleft !== b.left || btop !== b.top; if (aclipped && bclipped) return undefined; const clipped = aclipped || bclipped; + const pt1inside = NumCast(LinkDocs[0].link_anchor_1_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_1_y) % 100 !== 0; + const pt2inside = NumCast(LinkDocs[0].link_anchor_2_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_2_y) % 100 !== 0; const pt1 = [aleft + a.width / 2, atop + a.height / 2]; const pt2 = [bleft + b.width / 2, btop + b.width / 2]; - const pt1vec = [(bDocBounds.left + bDocBounds.right) / 2 - pt1[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt1[1]]; - const pt2vec = [(aDocBounds.left + aDocBounds.right) / 2 - pt2[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt2[1]]; + const pt2vec = pt2inside ? [-0.7071, 0.7071] : [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]]; + const pt1vec = pt1inside ? [-0.7071, 0.7071] : [(aDocBounds.left + aDocBounds.right) / 2 - pt1[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt1[1]]; const pt1len = Math.sqrt(pt1vec[0] * pt1vec[0] + pt1vec[1] * pt1vec[1]); const pt2len = Math.sqrt(pt2vec[0] * pt2vec[0] + pt2vec[1] * pt2vec[1]); const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2; - const pt1norm = clipped ? [0, 0] : [(pt1vec[0] / pt1len) * ptlen, (pt1vec[1] / pt1len) * ptlen]; - const pt2norm = clipped ? [0, 0] : [(pt2vec[0] / pt2len) * ptlen, (pt2vec[1] / pt2len) * ptlen]; + const pt1norm = clipped ? [0, 0] : [-(pt1vec[0] / pt1len) * ptlen, -(pt1vec[1] / pt1len) * ptlen]; + const pt2norm = clipped ? [0, 0] : [-(pt2vec[0] / pt2len) * ptlen, -(pt2vec[1] / pt2len) * ptlen]; const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1; const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1; const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen]; @@ -215,53 +224,84 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc); const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc); - const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX); - const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY); - return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13] }; + const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX); + const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY); + return { + a, + b, + pt1norm, + pt2norm, + aActive, + bActive, + textX, + textY, + pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], + pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13], + }; } render() { if (!this.renderData) return null; + const link = this.props.LinkDocs[0]; const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; - LinkManager.currentLink = this.props.LinkDocs[0]; - const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship - const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>; - const linkColorList = Doc.UserDoc().linkColorList as List<string>; - const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>; + const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship + const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>; + const linkColorList = Doc.UserDoc().link_ColorList as List<string>; + const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>; const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + const linkDescription = Field.toString(link.link_description as any as Field); const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; //access stroke color using index of the relationship in the color list (default black) - const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? 'black' : linkColorList[currRelationshipIndex]; + const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex]; // const hexStroke = this.rgbToHex(stroke) //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) //thickness varies linearly from 3px to 12px for increasing link count const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px'; - if (this.props.LinkDocs[0].displayArrow === undefined) { - this.props.LinkDocs[0].displayArrow = false; + if (link.link_displayArrow === undefined) { + link.link_displayArrow = false; } - return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || (!this.props.LinkDocs[0].linkDisplay && !aActive && !bActive) ? null : ( + return link.opacity === 0 || !a.width || !b.width || (!link.link_displayLine && !aActive && !bActive) ? null : ( <> <defs> - <marker id="arrowhead" markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto"> - <polygon points="0 0, 3 1.5, 0 3" fill={Colors.DARK_GRAY} /> + <marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto"> + <polygon points="0 0, 3 1.5, 0 3" fill={stroke} /> </marker> + <filter id="outline"> + <feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" /> + <feFlood floodColor={`${Colors.DARK_GRAY}`} /> + <feComposite in2="expanded" operator="in" /> + <feComposite in="SourceGraphic" /> + </filter> + <filter x="0" y="0" width="1" height="1" id={`${link[Id] + 'background'}`}> + <feFlood floodColor={`${StrCast(link._backgroundColor, 'white')}`} result="bg" /> + <feMerge> + <feMergeNode in="bg" /> + <feMergeNode in="SourceGraphic" /> + </feMerge> + </filter> </defs> <path + filter={LinkManager.currentLink === link ? 'url(#outline)' : ''} + fill="pink" + stroke="antiquewhite" + strokeWidth="4" className="collectionfreeformlinkview-linkLine" - style={{ pointerEvents: 'all', opacity: this._opacity, stroke: SelectionManager.SelectedSchemaDoc() === this.props.LinkDocs[0] ? Colors.MEDIUM_BLUE : stroke, strokeWidth }} + style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }} onClick={this.onClickLine} d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} - markerEnd={this.props.LinkDocs[0].displayArrow ? 'url(#arrowhead)' : ''} + markerEnd={link.link_displayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''} /> - {textX === undefined ? null : ( - <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}> - {Field.toString(this.props.LinkDocs[0].description as any as Field)} + {textX === undefined || !linkDescription ? null : ( + <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}> + <tspan> </tspan> + <tspan dy="2">{linkDescription}</tspan> + <tspan dy="2"> </tspan> </text> )} </> |
