diff options
Diffstat (limited to 'src/client/views/nodes/LinkAnchorBox.tsx')
-rw-r--r-- | src/client/views/nodes/LinkAnchorBox.tsx | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx new file mode 100644 index 000000000..0a4325d8c --- /dev/null +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -0,0 +1,115 @@ +import { action, computed, makeObservable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { Doc } from '../../../fields/Doc'; +import { NumCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { DragManager, dropActionType } from '../../util/DragManager'; +import { LinkFollower } from '../../util/LinkFollower'; +import { SelectionManager } from '../../util/SelectionManager'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import { StyleProp } from '../StyleProvider'; +import { FieldView, FieldViewProps } from './FieldView'; +import './LinkAnchorBox.scss'; +import { LinkInfo } from './LinkDocPreview'; +const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore +@observer +export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(LinkAnchorBox, fieldKey); + } + _doubleTap = false; + _lastTap: number = 0; + _ref = React.createRef<HTMLDivElement>(); + _isOpen = false; + _timeout: NodeJS.Timeout | undefined; + + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + + componentDidMount() { + this._props.setContentViewBox?.(this); + } + + @computed get linkSource() { + return this.DocumentView?.().containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource); + } + + onPointerDown = (e: React.PointerEvent) => { + const linkSource = this.linkSource; + linkSource && + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => { + if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false); + else this._props.select(false); + }); + }; + onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { + const cdiv = this._ref?.current?.parentElement; + if (!this._isOpen && cdiv) { + const bounds = cdiv.getBoundingClientRect(); + const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); + const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); + if (separation > 100) { + const dragData = new DragManager.DocumentDragData([this.Document]); + dragData.dropAction = dropActionType.embed; + dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick']; + DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]); + return true; + } else { + this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100; + this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100; + this.layoutDoc.link_autoMoveAnchors = false; + } + } + return false; + }); + + specificContextMenu = (e: React.MouseEvent): void => {}; + + render() { + TraceMobx(); + const small = this._props.PanelWidth() <= 1; // this happens when rendered in a treeView + const x = NumCast(this.layoutDoc[this.fieldKey + '_x'], 100); + const y = NumCast(this.layoutDoc[this.fieldKey + '_y'], 100); + const background = this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.BackgroundColor + ':anchor'); + const anchor = this.fieldKey === 'link_anchor_1' ? 'link_anchor_2' : 'link_anchor_1'; + const anchorScale = !this.dataDoc[this.fieldKey + '_useSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; + const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title); + const selView = SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_1') + ? 'link_anchor_1' + : SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_2') + ? 'link_anchor_2' + : ''; + return ( + <div + ref={this._ref} + title={targetTitle} + className={`linkAnchorBox-cont${small ? '-small' : ''}`} + onPointerEnter={e => + LinkInfo.SetLinkInfo({ + DocumentView: this.DocumentView, + styleProvider: this._props.styleProvider, + linkSrc: this.linkSource, + linkDoc: this.Document, + showHeader: true, + location: [e.clientX, e.clientY + 20], + noPreview: false, + }) + } + onPointerDown={this.onPointerDown} + onContextMenu={this.specificContextMenu} + style={{ + border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${MEDIUM_GRAY} 2px` : undefined, + background, + left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`, + top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`, + transform: `scale(${anchorScale})`, + cursor: 'grab', + }} + /> + ); + } +} |