aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/LinkBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/LinkBox.tsx')
-rw-r--r--src/client/views/nodes/LinkBox.tsx108
1 files changed, 106 insertions, 2 deletions
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index efb949a47..d871c88ba 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,11 +1,19 @@
import React = require('react');
+import { Bezier } from 'bezier-js';
+import { computed, action } from 'mobx';
import { observer } from 'mobx-react';
-import { emptyFunction, returnAlways, returnFalse, returnTrue } from '../../../Utils';
+import { Height, Width } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
+import { DocCast, StrCast } from '../../../fields/Types';
+import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
+import { DocumentManager } from '../../util/DocumentManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkBox.scss';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { Transform } from '../../util/Transform';
@observer
export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -17,8 +25,104 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
componentDidMount() {
this.props.setContentView?.(this);
}
+ @computed get anchor1() {
+ const anchor1 = DocCast(this.rootDoc.link_anchor_1);
+ const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
+ return DocumentManager.Instance.getDocumentView(anchor_1);
+ }
+ @computed get anchor2() {
+ const anchor2 = DocCast(this.rootDoc.link_anchor_2);
+ const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
+ return DocumentManager.Instance.getDocumentView(anchor_2);
+ }
+ screenBounds = () => {
+ if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
+ const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
+ const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
+ const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(this.anchor1.rootDoc[Width](), this.anchor1.rootDoc[Height]()) };
+ const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(this.anchor2.rootDoc[Width](), this.anchor2.rootDoc[Height]()) };
+
+ const pts = [] as number[][];
+ pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
+ pts.push(Utils.getNearestPointInPerimeter(a_scrBds.tl[0], a_scrBds.tl[1], a_scrBds.br[0] - a_scrBds.tl[0], a_scrBds.br[1] - a_scrBds.tl[1], (b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2));
+ pts.push(Utils.getNearestPointInPerimeter(b_scrBds.tl[0], b_scrBds.tl[1], b_scrBds.br[0] - b_scrBds.tl[0], b_scrBds.br[1] - b_scrBds.tl[1], (a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2));
+ pts.push([(b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2]);
+ const agg = aggregateBounds(
+ pts.map(pt => ({ x: pt[0], y: pt[1] })),
+ 0,
+ 0
+ );
+ return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
+ }
+ return { left: 0, top: 0, right: 0, bottom: 0, center: undefined };
+ };
render() {
- if (this.dataDoc.treeView_Open === undefined) setTimeout(() => (this.dataDoc.treeView_Open = true));
+ if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
+ const a = (this.anchor1 ?? this.anchor2)!;
+ const b = (this.anchor2 ?? this.anchor1)!;
+
+ const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
+ const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
+ const a_invXf = a.props.ScreenToLocalTransform().inverse();
+ const b_invXf = b.props.ScreenToLocalTransform().inverse();
+ const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) };
+ const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) };
+ const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
+ const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
+
+ const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2];
+ const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2);
+ const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2);
+ const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2];
+
+ const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]);
+ const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])];
+ setTimeout(
+ action(() => {
+ this.layoutDoc.x = lx;
+ this.layoutDoc.y = ty;
+ this.layoutDoc._width = rx - lx;
+ this.layoutDoc._height = by - ty;
+ })
+ );
+
+ const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+ const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+
+ const bez = new Bezier(pts.map(p => ({ x: p[0], y: p[1] })));
+ const text = bez.get(0.5);
+ const linkDesc = StrCast(this.rootDoc.link_description) || 'description';
+ return (
+ <div style={{ pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
+ <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ overflow: 'visible' }}>
+ <defs>
+ <filter x="0" y="0" width="1" height="1" id={`${this.rootDoc[Id] + 'background'}`}>
+ <feFlood floodColor={`${StrCast(this.rootDoc._backgroundColor, 'lightblue')}`} result="bg" />
+ <feMerge>
+ <feMergeNode in="bg" />
+ <feMergeNode in="SourceGraphic" />
+ </feMerge>
+ </filter>
+ </defs>
+ <path
+ className="collectionfreeformlinkview-linkLine"
+ style={{ pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', stroke: highlightColor ?? 'lightblue', strokeWidth: 4 }}
+ d={`M ${pts[1][0] - lx} ${pts[1][1] - ty} C ${pts[1][0] + pts[1][0] - pts[0][0] - lx} ${pts[1][1] + pts[1][1] - pts[0][1] - ty},
+ ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
+ />
+ <text
+ filter={`url(#${this.rootDoc[Id] + 'background'})`}
+ style={{ pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
+ x={text.x - lx}
+ y={text.y - ty}>
+ <tspan>&nbsp;</tspan>
+ <tspan dy="2">{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}</tspan>
+ <tspan dy="2">&nbsp;</tspan>
+ </text>
+ </svg>
+ </div>
+ );
+ }
return (
<div className={`linkBox-container${this.props.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
<ComparisonBox