aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/LinkBox.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-10-21 00:41:23 -0400
committerbobzel <zzzman@gmail.com>2023-10-21 00:41:23 -0400
commit661c1367d27fa23c3aeb62369e92cd36eb5edabd (patch)
tree6887e62707fae03149bc2bbaec38c30e2a944f82 /src/client/views/nodes/LinkBox.tsx
parent3ba733ffffb3036ea941bdbb5baf4c79bc7764af (diff)
change to doc decorations to be more "lightweight". made linkBox render links in a freeform view as a DocView. added an auto-reset view option for freeforms. fixed highlighting ink strokes. Made groups behave better for selecting things 'inside' the group bounding box that aren't in the group. Added vertically centered text option.
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