diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Utils.ts | 23 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 4 | ||||
-rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 10 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 3 | ||||
-rw-r--r-- | src/client/views/InkingControl.tsx | 12 | ||||
-rw-r--r-- | src/client/views/nodes/DocuLinkView.tsx | 85 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 114 |
7 files changed, 145 insertions, 106 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 6889936b8..66ed8be5d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -149,6 +149,29 @@ export namespace Utils { } + export function clamp(n: number, lower: number, upper: number) { + return Math.max(lower, Math.min(upper, n)); + } + + export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) { + var r = l + w, + b = t + h; + + var x = clamp(x, l, r), + y = clamp(y, t, b); + + var dl = Math.abs(x - l), + dr = Math.abs(x - r), + dt = Math.abs(y - t), + db = Math.abs(y - b); + + var m = Math.min(dl, dr, dt, db); + + return (m === dt) ? [x, t] : + (m === db) ? [x, b] : + (m === dl) ? [l, y] : [r, y]; + } + export function GetClipboardText(): string { var textArea = document.createElement("textarea"); document.body.appendChild(textArea); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 06dab024e..576b16bc8 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -87,12 +87,12 @@ export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: num } } -export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) { +export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc, singleLink?: Doc) { let srcTarg = sourceDoc.proto; let draggedDocs: Doc[] = []; if (srcTarg) { - let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg); + let linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg); if (linkDocs) { draggedDocs = linkDocs.map(link => { let opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 959b120ed..ba87ecfb4 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -47,7 +47,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], private _aliasButton = React.createRef<HTMLDivElement>(); private _tooltipoff = React.createRef<HTMLDivElement>(); private _textDoc?: Doc; - private _linkDrag?: UndoManager.Batch; public static Instance: DocumentButtonBar; constructor(props: { views: DocumentView[] }) { @@ -139,15 +138,10 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let selDoc = this.props.views[0]; let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined; let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); - this._linkDrag = UndoManager.StartBatch("Drag Link"); + let _linkDrag = UndoManager.StartBatch("Drag Link"); DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { handlers: { - dragComplete: () => { - if (this._linkDrag) { - this._linkDrag.end(); - this._linkDrag = undefined; - } - }, + dragComplete: () => _linkDrag && _linkDrag.end() }, hideSource: false }); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6e8ba2d3d..927729487 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -167,7 +167,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> get Bounds(): { x: number, y: number, b: number, r: number } { let x = this._forceUpdate; this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { - if (documentView.props.renderDepth === 0 || + if (documentView._selectedLink !== -1 || + documentView.props.renderDepth === 0 || Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) { return bounds; } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index b8f3c2875..1aacdc8ec 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -81,18 +81,18 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - (!ruleProvider && targetDoc) && (targetDoc.backgroundColor = matchedColor); + (!ruleProvider && targetDoc) && (view.setBackround(matchedColor)); return { target: targetDoc, previous: oldColor }; }); - let captured = this._selectedColor; - UndoManager.AddEvent({ - undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous), - redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured) - }); + //let captured = this._selectedColor; + // UndoManager.AddEvent({ + // undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous), + // redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured) + // }); } else { CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor); } diff --git a/src/client/views/nodes/DocuLinkView.tsx b/src/client/views/nodes/DocuLinkView.tsx new file mode 100644 index 000000000..622d41047 --- /dev/null +++ b/src/client/views/nodes/DocuLinkView.tsx @@ -0,0 +1,85 @@ +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, Opt } from "../../../new_fields/Doc"; +import { NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils } from '../../../Utils'; +import { DocumentManager } from "../../util/DocumentManager"; +import "./DocumentView.scss"; +import React = require("react"); +import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; +import { UndoManager } from "../../util/UndoManager"; + + +interface DocuLinkViewProps { + Document: Doc; + isSelected: () => boolean; + addDocTab: (doc: Doc, dataDoc: Opt<Doc>, where: string) => void; + anchor: string; + otherAnchor: string; + scale: () => number; + contentDiv: HTMLDivElement | null; + link: Doc; + index: number; + selectedLink: () => number; + selectLink: (id: number) => void; + blacklist: Opt<Doc> +} + +@observer +export class DocuLinkView extends React.Component<DocuLinkViewProps> { + _downx = 0; + _downy = 0; + @observable _x = 0; + @observable _y = 0; + @observable _selected = false; + _ref = React.createRef<HTMLDivElement>(); + + onPointerDown = (e: React.PointerEvent) => { + this._downx = e.clientX; + this._downy = e.clientY; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + } + onPointerMove = action((e: PointerEvent) => { + if (this.props.contentDiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) { + let bounds = this.props.contentDiv.getBoundingClientRect(); + let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); + let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); + let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)) + if (separation > 100) { + DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.Document, this.props.link); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } else if (dragdist > separation) { + this.props.link[this.props.anchor + "_x"] = (pt[0] - bounds.left) / bounds.width * 100; + this.props.link[this.props.anchor + "_y"] = (pt[1] - bounds.top) / bounds.height * 100; + } + } + }) + onPointerUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey)) { + this.props.selectLink(this.props.selectedLink() === this.props.index ? -1 : this.props.index); + } + } + onClick = (e: React.MouseEvent) => { + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey)) { + DocumentManager.Instance.FollowLink(this.props.link, this.props.link[this.props.anchor] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); + } + e.stopPropagation(); + } + render() { + let y = NumCast(this.props.link[this.props.anchor + "_y"], 100); + let x = NumCast(this.props.link[this.props.anchor + "_x"], 100); + let c = StrCast(this.props.link[this.props.anchor + "_background"], "lightblue"); + return <div onPointerDown={this.onPointerDown} onClick={this.onClick} title={StrCast((this.props.link[this.props.otherAnchor]! as Doc).title)} ref={this._ref} style={{ + cursor: "default", position: "absolute", background: c, width: "25px", height: "25px", borderRadius: "20px", textAlign: "center", left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`, + transform: `scale(${1 / this.props.scale()})`, + border: this.props.selectedLink() === this.props.index && this.props.isSelected() ? "solid 3px black" : undefined + }} /> + } +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c217c9d86..d8a1841b1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,6 +40,7 @@ import SharingManager from '../../util/SharingManager'; import { Scripting } from '../../util/Scripting'; import { DictationOverlay } from '../DictationOverlay'; import { CollectionViewType } from '../collections/CollectionBaseView'; +import { DocuLinkView } from './DocuLinkView'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -241,6 +242,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble && e.button === 0) return; + runInAction(() => this._selectedLink = -1); this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; @@ -626,6 +628,25 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu layoutKey="layout" DataDoc={this.props.DataDoc} />); } + linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor1" : "anchor2"; + linkOtherEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor2" : "anchor1"; + public setBackround(color: string) { + let selLink = this._selectedLink !== -1 && DocListCast(this.Document.links)[this._selectedLink]; + if (selLink) { + let both = selLink["anchor1_background"] === selLink["anchor2_background"]; + selLink[this.linkEndpoint(selLink) + "_background"] = color; + both && (selLink[this.linkOtherEndpoint(selLink) + "_background"] = color); + } else { + this.Document.backgroundColor = color; + } + } + @observable _selectedLink = -1; + selectLink = action((which: number) => { + SelectionManager.SelectDoc(this, false); + this._selectedLink = which; + }) + selectedLink = () => this._selectedLink; + render() { if (!this.props.Document) return (null); let animDims = this.props.Document.animateToDimensions ? Array.from(Cast(this.props.Document.animateToDimensions, listSpec("number"))!) : undefined; @@ -696,7 +717,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > - {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => <DocuLink view={this} link={d} index={i} />)} + {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => + <DocuLinkView Document={this.props.Document} blacklist={this.props.ContainingCollectionDoc} anchor={this.linkEndpoint(d)} otherAnchor={this.linkOtherEndpoint(d)} addDocTab={this.props.addDocTab} contentDiv={this.ContentDiv} + scale={this.props.ContentScaling} + isSelected={this.isSelected} link={d} index={i} selectLink={this.selectLink} selectedLink={this.selectedLink} />)} {!showTitle && !showCaption ? this.Document.searchFields ? (<div className="documentView-searchWrapper"> @@ -720,94 +744,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - -interface DocuLinkProps { - view: DocumentView; - link: Doc; - index: number; -} - -@observer -export class DocuLink extends React.Component<DocuLinkProps> { - _downx = 0; - _downy = 0; - @observable _x = 0; - @observable _y = 0; - - constructor(props: DocuLinkProps) { - super(props); - } - - clamp(n: number, lower: number, upper: number) { - return Math.max(lower, Math.min(upper, n)); - } - - getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) { - var r = l + w, - b = t + h; - - var x = this.clamp(x, l, r), - y = this.clamp(y, t, b); - - var dl = Math.abs(x - l), - dr = Math.abs(x - r), - dt = Math.abs(y - t), - db = Math.abs(y - b); - - var m = Math.min(dl, dr, dt, db); - - return (m === dt) ? [x, t] : - (m === db) ? [x, b] : - (m === dl) ? [l, y] : [r, y]; - } - - get linkEndpoint() { - return Doc.AreProtosEqual(this.props.view.props.Document, Cast(this.props.link.anchor1, Doc) as Doc) ? - "anchor1" : "anchor2"; - } - get linkOtherEndpoint() { - return !Doc.AreProtosEqual(this.props.view.props.Document, Cast(this.props.link.anchor1, Doc) as Doc) ? - "anchor1" : "anchor2"; - } - - onPointerDown = (e: React.PointerEvent) => { - this._downx = e.clientX; - this._downy = e.clientY; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - } - onPointerMove = action((e: PointerEvent) => { - if (this.props.view.ContentDiv && (Math.abs(e.clientX - this._downx) > 3 || Math.abs(e.clientY - this._downy) > 3)) { - let bounds = this.props.view.ContentDiv.getBoundingClientRect(); - let pt = this.getNearestPointInPerimeter(bounds.left - 25, bounds.top - 25, bounds.width, bounds.height, e.clientX, e.clientY); - this.props.link[this.linkEndpoint + "_x"] = (pt[0] - bounds.left) / bounds.width * 100; - this.props.link[this.linkEndpoint + "_y"] = (pt[1] - bounds.top) / bounds.height * 100; - } - }) - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - onClick = (e: React.MouseEvent) => { - if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3) { - DocumentManager.Instance.FollowLink(this.props.link, this.props.view.props.Document, document => this.props.view.props.addDocTab(document, undefined, "inTab"), false); - } - e.stopPropagation(); - } - render() { - return <div onPointerDown={this.onPointerDown} title={StrCast((this.props.link[this.linkOtherEndpoint]! as Doc).title)} style={{ - position: "absolute", background: "lightblue", width: "25px", height: "25px", borderRadius: "20px", - left: `${NumCast(this.props.link[this.linkEndpoint + "_x"], 100)}%`, top: `${NumCast(this.props.link[this.linkEndpoint + "_y"], 100)}%` - }} > - {this.props.index} - </div> - } -} - - export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField: string, oldLayout?: Doc) { let oldLayoutExt = oldLayout || await Cast(doc[oldLayoutField], Doc); if (oldLayoutExt) { |