aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/util/DragManager.ts2
-rw-r--r--src/client/util/SelectionManager.ts8
-rw-r--r--src/client/views/DocComponent.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx12
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx15
-rw-r--r--src/client/views/nodes/DocumentView.tsx24
-rw-r--r--src/client/views/nodes/LinkBox.tsx213
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx7
9 files changed, 122 insertions, 162 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 95058da22..1978c144b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -594,7 +594,6 @@ export namespace Docs {
layout: { view: LinkBox, dataField: 'link' },
options: {
childDontRegisterViews: true,
- onClick: FollowLinkScript(),
layout_hideLinkAnchors: true,
_height: 1,
_width: 1,
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 071b25a0e..a6ad0f1b3 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -243,7 +243,7 @@ export namespace DragManager {
};
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
StartDrag(eles, dragData, downX, downY, options, finishDrag);
- dragData.draggedViews.forEach(view => view.props.dragStarting?.(dragData));
+ dragData.draggedViews.forEach(view => view.props.dragStarting?.());
return true;
}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index eddc7fc16..36b926053 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -52,9 +52,11 @@ export class SelectionManager {
public static DeselectAll = (except?: Doc): void => {
const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
- LinkManager.Instance.currentLink = undefined;
- LinkManager.Instance.currentLinkAnchor = undefined;
- runInAction(() => (this.Instance.SelectedSchemaDocument = undefined));
+ runInAction(() => {
+ LinkManager.Instance.currentLink = undefined;
+ LinkManager.Instance.currentLinkAnchor = undefined;
+ this.Instance.SelectedSchemaDocument = undefined;
+ });
this.Instance.SelectedViews.forEach(dv => {
dv.IsSelected = false;
dv._props.whenChildContentsActiveChanged(false);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 3c772bd42..dacd359c5 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -50,7 +50,7 @@ export interface ViewBoxInterface {
dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag)
incrementalRendering?: () => void;
infoUI?: () => JSX.Element | null;
- screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
+ screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }>;
ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 89e72152a..54314f62c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -135,14 +135,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
+ const trans = () => this.getDocTransition(d);
// assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
// just getting the style
- const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+ const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, transition: trans(), width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
// So we're choosing whether we're going to render a column or a masonry doc
return (
<div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
- {this.getDisplayDoc(d, width, i)}
+ {this.getDisplayDoc(d, width, trans, i)}
</div>
);
});
@@ -309,7 +310,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
- getDisplayDoc(doc: Doc, width: () => number, count: number) {
+ getDisplayDoc(doc: Doc, width: () => number, trans: () => string, count: number) {
const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
const height = () => this.getDocHeight(doc);
const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight()));
@@ -330,6 +331,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
layout_fitWidth={this.childFitWidth}
isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
onKey={this.onKeyDown}
+ DataTransition={trans}
onBrowseClickScript={this._props.onBrowseClickScript}
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
@@ -379,6 +381,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return maxWidth;
}
+ getDocTransition(d?: Doc) {
+ if (!d) return '';
+ return StrCast(d.dataTransition);
+ }
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0ae4ed62c..a0a64ab59 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -33,7 +33,6 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
opacity?: number;
highlight?: boolean;
transition?: string;
- dataTransition?: string;
RenderCutoffProvider: (doc: Doc) => boolean;
CollectionFreeFormView: CollectionFreeFormView;
}
@@ -56,7 +55,6 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon
@observable Height = this.props.height;
@observable AutoDim = this.props.autoDim;
@observable Transition = this.props.transition;
- @observable DataTransition = this.props.dataTransition;
CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
@@ -83,7 +81,6 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon
w_Height = () => this.Height; // prettier-ignore
w_AutoDim = () => this.AutoDim; // prettier-ignore
w_Transition = () => this.Transition; // prettier-ignore
- w_DataTransition = () => this.DataTransition; // prettier-ignore
PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
@@ -117,7 +114,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
w_Transition: () => string | undefined;
w_Width: () => number;
w_Height: () => number;
- w_DataTransition: () => string | undefined;
PanelWidth: () => number;
PanelHeight: () => number;
RenderCutoffProvider: (doc: Doc) => boolean;
@@ -288,14 +284,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this._props.PanelWidth(),
height: this._props.PanelHeight(),
transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
- transition: this._props.w_Transition?.() ?? (this._props.w_DataTransition?.() || this._props.w_Transition?.()),
+ transition: this._props.w_Transition?.() || StrCast(this.Document.dataTransition),
zIndex: this._props.w_ZIndex?.(),
display: this._props.w_Width?.() ? undefined : 'none',
}}>
{this._props.RenderCutoffProvider(this.Document) ? (
<div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
) : (
- <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
+ <DocumentView
+ {...passOnProps}
+ DataTransition={this._props.w_Transition}
+ CollectionFreeFormDocumentView={this.returnThis}
+ styleProvider={this.styleProvider}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ isGroupActive={this.isGroupActive}
+ />
)}
</div>
);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 02f756f16..5efa028d1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -104,6 +104,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
dontHideOnDrag?: boolean;
suppressSetHeight?: boolean;
onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+ DataTransition?: () => string | undefined;
NativeWidth?: () => number;
NativeHeight?: () => number;
contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
@@ -116,7 +117,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
// this makes mobx trace() statements more descriptive
public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
-
/**
* This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
* needing to know about/reference MainView
@@ -933,7 +933,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
fontSize: Cast(this.Document._text_fontSize, 'string', null),
transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
- transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+ transition: !this._animateScalingTo ? this._props.DataTransition?.() || StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
}}>
{this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
this.viewBoxContents()
@@ -1075,6 +1075,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewTimer: NodeJS.Timeout | undefined;
private _animEffectTimer: NodeJS.Timeout | undefined;
+ public Guid = Utils.GenerateGuid(); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
@computed public static get exploreMode() {
return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
@@ -1151,6 +1152,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
componentWillUnmount() {
+ this._viewTimer && clearTimeout(this._viewTimer);
runInAction(() => this.Document[DocViews].delete(this));
Object.values(this._disposers).forEach(disposer => disposer?.());
!BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
@@ -1174,21 +1176,23 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
}
- @computed get getBounds() {
- if (!this._docViewInternal?._contentDiv || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
+ @computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> {
+ if (!this.ContentDiv || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
return undefined;
}
- if (this._docViewInternal._componentView?.screenBounds?.()) {
- return this._docViewInternal._componentView.screenBounds();
+ if (this.ComponentView?.screenBounds?.()) {
+ return this.ComponentView.screenBounds();
}
const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const docuBox = this._docViewInternal._contentDiv.getElementsByClassName('linkAnchorBox-cont');
- if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
+ const docuBox = this.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+ if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined };
}
- return { left, top, right, bottom };
+ // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox
+ const transition = this.docViewPath().find((parent: DocumentView) => parent._props.DataTransition?.() || StrCast(parent.Document.dataTransition));
+ return { left, top, right, bottom, transition: transition?._props.DataTransition?.() || StrCast(transition?.Document.dataTransition) };
}
@computed get nativeWidth() {
@@ -1385,7 +1389,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
- <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+ <div id={this.Guid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 07511d0ec..85d22abfd 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,119 +1,67 @@
-import { Bezier } from 'bezier-js';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Id } from '../../../fields/FieldSymbols';
+import Xarrow from 'react-xarrows';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { aggregateBounds, emptyFunction, lightOrDark, returnAlways, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, lightOrDark, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
-import { Transform } from '../../util/Transform';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { LinkManager } from '../../util/LinkManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkBox.scss';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
-import { LinkManager } from '../../util/LinkManager';
@observer
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 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) {
super(props);
makeObservable(this);
}
+ @computed get anchor1() { return this.anchor(1); } // prettier-ignore
+ @computed get anchor2() { return this.anchor(2); } // prettier-ignore
- onClickScriptDisable = returnAlways;
- @computed get anchor1() {
- const anchor1 = DocCast(this.dataDoc.link_anchor_1);
- const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
- return DocumentManager.Instance.getDocumentView(anchor_1, this.DocumentView?.().containerViewPath?.().lastElement());
- }
- @computed get anchor2() {
- const anchor2 = DocCast(this.dataDoc.link_anchor_2);
- const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
- return DocumentManager.Instance.getDocumentView(anchor_2, this.DocumentView?.().containerViewPath?.().lastElement());
- }
- screenBounds = () => {
- if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
- const a_invXf = this.anchor1.screenToViewTransform().inverse();
- const b_invXf = this.anchor2.screenToViewTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._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 undefined;
+ anchor = (which: number) => {
+ const anch = DocCast(this.dataDoc['link_anchor_' + which]);
+ const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
+ return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
- disposer: IReactionDisposer | undefined;
+ componentWillUnmount(): void {
+ this.disposer?.();
+ }
componentDidMount() {
this._props.setContentViewBox?.(this);
this.disposer = reaction(
- () => {
- if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
- const a = (this.anchor1 ?? this.anchor2)!;
- const b = (this.anchor2 ?? this.anchor1)!;
-
- const parxf = this.DocumentView?.().containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
- const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
- const a_invXf = a.screenToViewTransform().inverse();
- const b_invXf = b.screenToViewTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._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])];
- return { pts, lx, rx, ty, by };
- }
- return undefined;
- },
- params => {
- this.renderProps = params;
- if (params) {
- if (
- Math.abs(params.lx - NumCast(this.layoutDoc.x)) > 1e-5 ||
- Math.abs(params.ty - NumCast(this.layoutDoc.y)) > 1e-5 ||
- Math.abs(params.rx - params.lx - NumCast(this.layoutDoc._width)) > 1e-5 ||
- Math.abs(params.by - params.ty - NumCast(this.layoutDoc._height)) > 1e-5
- ) {
- this.layoutDoc.x = params?.lx;
- this.layoutDoc.y = params?.ty;
- this.layoutDoc._width = Math.max(1, params.rx - params?.lx);
- this.layoutDoc._height = Math.max(1, params?.by - params?.ty);
- }
- } else {
- this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width));
- this.layoutDoc._height = Math.max(50, NumCast(this.layoutDoc._height));
- }
+ () => ({ drag: SnappingManager.IsDragging, a: this.anchor1, b: this.anchor2 }),
+ ({ drag, a, b }) => {
+ setTimeout(
+ // need to wait for drag manager to set 'hidden' flag on dragged elements
+ action(() => {
+ let a1 = a && document.getElementById(a.Guid);
+ let a2 = b && document.getElementById(b.Guid);
+ if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
+ else {
+ for (; a1 && !a1.hidden; a1 = a1.parentElement);
+ for (; a2 && !a2.hidden; a2 = a2.parentElement);
+ this._hide = a1 || a2 ? true : false;
+ }
+ })
+ );
},
{ fireImmediately: true }
);
}
- select = (ctrlKey: boolean, shiftKey: boolean) => {
- LinkManager.Instance.currentLink = this.Document;
- };
+ select = (ctrlKey: boolean, shiftKey: boolean) => (LinkManager.Instance.currentLink = this.Document);
descriptionDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
@@ -121,7 +69,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
e,
returnFalse,
returnFalse,
- () => {
+ action(() => {
LinkManager.Instance.currentLink = this.Document;
LinkDescriptionPopup.Instance.popupX = e.clientX;
LinkDescriptionPopup.Instance.popupY = e.clientY;
@@ -134,69 +82,66 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
LinkDescriptionPopup.Instance.popupY -= 40;
}
- },
+ }),
false
);
};
- componentWillUnmount(): void {
- this.disposer?.();
- }
- @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
render() {
- if (this.renderProps) {
+ trace();
+ const a = this.anchor1;
+ const b = this.anchor2;
+ this._forceAnimate;
+
+ if (a && b && !this._hide) {
+ const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
+ const bxf = b.screenToViewTransform();
+ const scale = a.CollectionFreeFormView === this.DocumentView?.().CollectionFreeFormView ? axf.Scale : bxf.Scale;
+ 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;
+ 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);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
const color = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
-
- const { pts, lx, ty, rx, by } = this.renderProps;
- const bez = new Bezier(pts.map(p => ({ x: p[0] - lx, y: p[1] - ty })));
- const { x, y } = bez.get(0.5);
- const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
- const strokeWidth = NumCast(this.Document.stroke_width, 4);
+ const { strokeWidth, stroke_startMarker, stroke_endMarker } = this.Document;
const dash = StrCast(this.Document.stroke_dash);
- const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
- const pointerEvents = this._props.pointerEvents?.() === 'none' ? 'none' : 'all';
const stroke = highlightColor ?? 'lightblue';
+
+ const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
+ const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
return (
- <div className="linkBox">
- <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)}>
- <defs>
- <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
- <feFlood floodColor={`${highlightColor ?? StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
- <feMerge>
- <feMergeNode in="bg" />
- <feMergeNode in="SourceGraphic" />
- </feMerge>
- </filter>
- </defs>
- <path
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={a.Guid}
+ end={b.Guid} //
+ strokeWidth={NumCast(strokeWidth, 4)}
+ dashness={dash ? true : false}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={stroke}
+ labels={
+ <div
+ onPointerDown={this.descriptionDown} //
style={{
- pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
- stroke,
- strokeDasharray,
- strokeWidth,
- }}
- d={`M ${bez.points[0].x} ${bez.points[0].y} C ${bez.points[1].x} ${bez.points[1].y},
- ${bez.points[2].x} ${bez.points[2].y}, ${bez.points[3].x} ${bez.points[3].y}`}
- />
- {!linkDesc.trim().length ? (
- <circle r={5} onPointerDown={this.descriptionDown} style={{ fill: stroke, pointerEvents }} cx={x} cy={y} />
- ) : (
- <text
- onPointerDown={this.descriptionDown} //
- filter={`url(#${this.Document[Id] + 'background'})`}
- style={{ pointerEvents, background: stroke }}
- x={x}
- y={y}>
- <tspan style={{ fontSize, fontFamily, strokeWidth: 0, background: stroke, fill: color || lightOrDark(stroke) }}>&nbsp;{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}&nbsp;</tspan>
- </text>
- )}
- </svg>
- </div>
+ borderRadius: '20%', //
+ padding: '3',
+ pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
+ background: stroke,
+ color: color || lightOrDark(stroke),
+ fontSize,
+ fontFamily /*, fontStyle: 'italic'*/,
+ }}>
+ {labelText}
+ </div>
+ }
+ passProps={{ onPointerDown: this.descriptionDown }}
+ />
);
}
+ return null;
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 1b2c45e72..b8f6575dd 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { Animation, DocData } from '../../../../fields/DocSymbols';
+import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
import { Copy, Id } from '../../../../fields/FieldSymbols';
import { InkField } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -414,7 +414,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget.rotation = NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation));
bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
- setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
+ bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
+ bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
changed = true;
}
}
@@ -441,7 +442,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const bestTargetData = bestTarget[DocData];
const current = bestTargetData[fkey];
const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
- if (hash) bestTargetData[fkey + '_' +hash] = current instanceof ObjectField ? current[Copy]() : current;
+ if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
}
bestTarget[fkey + '_usePath'] = activeItem.config_usePath;