diff options
author | Bob Zeleznik <zzzman@gmail.com> | 2020-04-04 21:30:38 -0400 |
---|---|---|
committer | Bob Zeleznik <zzzman@gmail.com> | 2020-04-04 21:30:38 -0400 |
commit | c291e8b2c8fb9034916b4eb4b286f99a8cb35a04 (patch) | |
tree | 92e5a58d36a4b03d423574ffba9d2e40eb725f28 | |
parent | 5338dfd24d0f6a65bb3131332746c1d2c82ef7f2 (diff) |
fixed link zoom following for inPlace and otherwise.
-rw-r--r-- | src/client/util/DocumentManager.ts | 90 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 7 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 10 | ||||
-rw-r--r-- | src/client/views/nodes/DocuLinkBox.tsx | 57 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 7 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 98 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBoxComment.tsx | 2 | ||||
-rw-r--r-- | src/client/views/pdf/Annotation.tsx | 2 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 2 | ||||
-rw-r--r-- | src/mobile/MobileInterface.tsx | 4 | ||||
-rw-r--r-- | src/new_fields/documentSchemas.ts | 3 | ||||
-rw-r--r-- | src/new_fields/util.ts | 2 |
12 files changed, 158 insertions, 126 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 63d864db5..ae392a35b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,11 +5,12 @@ import { List } from '../../new_fields/List'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; -import { DocumentView } from '../views/nodes/DocumentView'; +import { DocumentView, DocFocusFunc } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { Scripting } from './Scripting'; import { SelectionManager } from './SelectionManager'; import { DocumentType } from '../documents/DocumentTypes'; +import { UndoManager } from './UndoManager'; export class DocumentManager { @@ -84,24 +85,20 @@ export class DocumentManager { return this.getDocumentViewById(toFind[Id], preferredCollection); } - public getFirstDocumentView(toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined { - const views = this.getDocumentViews(toFind); - return views?.find(view => view.props.Document !== originatingDoc); + public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => { + return this.getDocumentViews(toFind)?.find(view => view.props.Document !== originatingDoc); } public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; + const docViews = DocumentManager.Instance.DocumentViews; - // heurstic to return the "best" documents first: + // heuristic to return the "best" documents first: // choose an exact match over an alias match // choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth) - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); return toReturn; } @@ -133,31 +130,47 @@ export class DocumentManager { return pairs; } - public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false, originatingDoc: Opt<Doc> = undefined): Promise<void> => { + static addRightSplit = (doc: Doc, finished?: () => void) => { + CollectionDockingView.AddRightSplit(doc); + finished?.(); + } + public jumpToDocument = async ( + targetDoc: Doc, + willZoom: boolean, + dockFunc = DocumentManager.addRightSplit, + docContext?: Doc, + linkId?: string, + closeContextIfNotFound: boolean = false, + originatingDoc: Opt<Doc> = undefined, + finished?: () => void): Promise<void> => { + const getFirstDocView = DocumentManager.Instance.getFirstDocumentView; + const finishFalse: DocFocusFunc = () => { finished?.(); return false; }; const highlight = () => { - const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - finalDocView && (finalDocView.Document.scrollToLinkID = linkId); - finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); + const finalDocView = getFirstDocView(targetDoc); + if (finalDocView) { + finalDocView.Document.scrollToLinkID = linkId; + Doc.linkFollowHighlight(finalDocView.props.Document); + } }; - const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc, originatingDoc); + const docView = getFirstDocView(targetDoc, originatingDoc); let annotatedDoc = await Cast(targetDoc.annotationOn, Doc); if (annotatedDoc) { - const first = DocumentManager.Instance.getFirstDocumentView(annotatedDoc); + const first = getFirstDocView(annotatedDoc); if (first) annotatedDoc = first.props.Document; } if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? - docView.props.focus(docView.props.Document, willZoom); + docView.props.focus(docView.props.Document, willZoom, undefined, finishFalse); highlight(); } else { const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; - const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; - const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc); + const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; + const targetDocContext = annotatedDoc || contextDoc; if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default - (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); + dockFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); highlight(); } else { - const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + const targetDocContextView = getFirstDocView(targetDocContext); targetDocContext.scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context targetDocContext.panTransformType = "Ease"; @@ -166,32 +179,33 @@ export class DocumentManager { // now find the target document within the context if (targetDoc.displayTimecode) { // the target should show up once the video scrubs to the display timecode; targetDocContext.currentTimecode = targetDoc.displayTimecode; + finished?.(); } else { setTimeout(() => { - const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc); + const retryDocView = getFirstDocView(targetDoc); if (retryDocView) { - retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context + retryDocView.props.focus(targetDoc, willZoom, undefined, finishFalse); // focus on the target if it now exists in the context } else { - if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); - targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); // otherwise create a new view of the target + if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document); + targetDoc.layout && dockFunc(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), finished); // otherwise create a new view of the target } highlight(); }, 0); } } else { // there's no context view so we need to create one first and try again - (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext); + dockFunc(targetDocContext); setTimeout(() => { - const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - const finalDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + const finalDocView = getFirstDocView(targetDoc); + const finalDocContextView = getFirstDocView(targetDocContext); setTimeout(() => // if not, wait a bit to see if the context can be loaded (e.g., a PDF). wait interval heurisitic tries to guess how we're animating based on what's just become visible - this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created. + this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true, undefined, finished), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created. }, 0); } } } } - public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { + public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, followLinkLocation: string, afterFocus?: DocFocusFunc) => void, zoom = false, reverse = false, currentContext?: Doc, finished?: () => void) { const linkDocs = link ? [link] : DocListCast(doc.links); SelectionManager.DeselectAll(); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); @@ -204,8 +218,10 @@ export class DocumentManager { const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined; const linkFollowDocContexts = first.length ? [await first[0].context as Doc, await first[0].context as Doc] : second.length ? [await second[0].context as Doc, await second[0].context as Doc] : [undefined, undefined]; const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2_timecode), NumCast(first[0].anchor1_timecode)] : second.length ? [NumCast(second[0].anchor1_timecode), NumCast(second[0].anchor2_timecode)] : [undefined, undefined]; + const Jump = DocumentManager.Instance.jumpToDocument; + const Focus = (where: string) => (doc: Doc, finished?: () => void) => focus(doc, where, () => { finished?.(); return false; }); if (linkFollowDocs && linkDoc) { - const maxLocation = StrCast(linkDoc.maximizeLocation, "inTab"); + const followLinkLocation = StrCast(linkDoc.followLinkLocation, "inTab"); const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; const target = linkFollowDocs[reverse ? 1 : 0]; const annotatedDoc = await Cast(target.annotationOn, Doc); @@ -214,9 +230,11 @@ export class DocumentManager { } else { target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]); } - DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id], undefined, doc); + Jump(linkFollowDocs[reverse ? 1 : 0], zoom, Focus(followLinkLocation), targetContext, linkDoc[Id], undefined, doc, finished); } else if (link) { - DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined); + Jump(link, zoom, Focus("onRight"), undefined, undefined, undefined, undefined, finished); + } else { + finished?.(); } } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 729101bc3..c96d6bed3 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -99,8 +99,8 @@ export class CollectionView extends Touchable<FieldViewProps> { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _isChildActive = false; //TODO should this be observable? - get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); }; - set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; }; + get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); } + set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; } @observable private _curLightboxImg = 0; @observable private static _safeMode = false; public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } @@ -395,6 +395,7 @@ export class CollectionView extends Touchable<FieldViewProps> { const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)"; return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name }); } + @computed get treeIgnoreFields() { return ["_facetCollection", "_docFilters"]; } @computed get filterView() { const facetCollection = this.props.Document; const flyout = ( @@ -422,7 +423,7 @@ export class CollectionView extends Touchable<FieldViewProps> { treeViewHideTitle={true} treeViewHideHeaderFields={true} onCheckedClick={this.scriptField!} - ignoreFields={["_facetCollection", "_docFilters"]} + ignoreFields={this.treeIgnoreFields} annotationsKey={""} dontRegisterView={true} PanelWidth={this.facetWidth} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 376d217bb..d4e44b9a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -842,9 +842,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { backgroundHalo: this.backgroundHalo, parentActive: this.props.active, bringToFront: this.bringToFront, + addDocTab: this.addDocTab, }; } + addDocTab = (doc: Doc, where: string) => { + if (where === "inPlace") { + this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); + return true; + } + return this.props.addDocTab(doc, where); + } getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData { const result = this.Document.arrangeScript?.script.run(params, console.log); if (result?.success) { @@ -1066,7 +1074,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (doc instanceof Doc) { const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); doc.x = xx, doc.y = yy; - this.props.addDocument && this.props.addDocument(doc); + this.props.addDocument?.(doc); } } } diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 81cf90f92..31ce58079 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -4,7 +4,7 @@ import { Doc, DocListCast } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils } from '../../../Utils'; +import { Utils, setupMoveUpEvents } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { DocComponent } from "../DocComponent"; @@ -16,6 +16,7 @@ import { ContextMenu } from "../ContextMenu"; import { LinkEditor } from "../linking/LinkEditor"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SelectionManager } from "../../util/SelectionManager"; +import { TraceMobx } from "../../../new_fields/util"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -29,8 +30,6 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc _doubleTap = false; _lastTap: number = 0; _ref = React.createRef<HTMLDivElement>(); - _downX = 0; - _downY = 0; _isOpen = false; _timeout: NodeJS.Timeout | undefined; @observable _x = 0; @@ -40,56 +39,42 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc @observable _forceOpen = false; 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.button === 0 && !e.ctrlKey) && e.stopPropagation(); + setupMoveUpEvents(this, e, this.onPointerMove, () => { }, this.onClick); } - onPointerMove = action((e: PointerEvent) => { + onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { const cdiv = this._ref && this._ref.current && this._ref.current.parentElement; - if (!this._isOpen && cdiv && (Math.abs(e.clientX - this._downX) > 5 || Math.abs(e.clientY - this._downY) > 5)) { + 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)); - const dragdist = Math.sqrt((pt[0] - this._downX) * (pt[0] - this._downX) + (pt[1] - this._downY) * (pt[1] - this._downY)); + const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1])); if (separation > 100) { - //DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack. const dragData = new DragManager.DocumentDragData([this.props.Document]); dragData.dropAction = "alias"; - dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y"]; - DragManager.StartDocumentDrag([this._ref.current!], dragData, this._downX, this._downY); - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); + dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isButton"]; + DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]); + return true; } else if (dragdist > separation) { this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100; this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100; } } + return false; }); - 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.Document.isButton)) { + @action + onClick = (e: PointerEvent) => { + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0); + this._lastTap = Date.now(); + if ((e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) { this.props.select(false); } - this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); - this._lastTap = Date.now(); - } - - @action - onClick = (e: React.MouseEvent) => { if (!this._doubleTap) { + const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack! need a better prop for passing the anchor's container this._editing = true; - this.props.ContainingCollectionDoc && this.props.bringToFront(this.props.ContainingCollectionDoc, false); - const {clientX, clientY} = e; - if (!this.props.Document.onClick && !this._isOpen) { + anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false); + if (anchorContainerDoc && !this.props.Document.onClick && !this._isOpen) { this._timeout = setTimeout(action(() => { - if (Math.abs(clientX - this._downX) < 3 && Math.abs(clientY - this._downY) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { - DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false); - } + DocumentManager.Instance.FollowLink(this.props.Document, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false); this._editing = false; }), 300 - (Date.now() - this._lastTap)); } @@ -97,7 +82,6 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc this._timeout && clearTimeout(this._timeout); this._timeout = undefined; } - e.stopPropagation(); } openLinkDocOnRight = (e: React.MouseEvent) => { @@ -126,6 +110,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc } render() { + TraceMobx(); const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0; const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0; const c = StrCast(this.props.Document.backgroundColor, "lightblue"); @@ -143,7 +128,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc </div> ); const small = this.props.PanelWidth() <= 1; - return <div className={`docuLinkBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu} + return <div className={`docuLinkBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} title={targetTitle} onContextMenu={this.specificContextMenu} ref={this._ref} style={{ background: c, left: !small ? `calc(${x}% - 7.5px)` : undefined, diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index d1d96f0a1..ce7bcd206 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -34,13 +34,18 @@ overflow-y: scroll; height: calc(100% - 20px); } - + .documentView-docuLinkAnchor { + display:flex; + overflow: hidden; + } .documentView-docuLinkWrapper { pointer-events: none; position: absolute; transform-origin: top left; width: 100%; height: 100%; + top:0; + left:0; z-index: 1; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 11bf5a6a7..f74255933 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -52,6 +52,7 @@ library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsA fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone); +export type DocFocusFunc = () => boolean; export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView>; ContainingCollectionDoc: Opt<Doc>; @@ -74,7 +75,7 @@ export interface DocumentViewProps { ContentScaling: () => number; PanelWidth: () => number; PanelHeight: () => number; - focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void; + focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void; parentActive: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; @@ -287,15 +288,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu Doc.UnBrushDoc(this.props.Document); } else if (this.onClickHandler?.script) { SelectionManager.DeselectAll(); - UndoManager.RunInBatch(() => this.onClickHandler!.script.run({ + const func = () => this.onClickHandler!.script.run({ this: this.props.Document, self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey - }, console.log) && !this.props.Document.isButton && this.select(false), "on click"); + }, console.log) && !this.props.Document.isButton && this.select(false); + if (this.props.Document !== Doc.UserDoc().undoBtn && this.props.Document !== Doc.UserDoc().redoBtn) { + UndoManager.RunInBatch(func, "on click"); + } else func(); } else if (this.Document.type === DocumentType.BUTTON) { UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click"); } else if (this.Document.isButton) { - UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow"); + DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey); } else { if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) { stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template @@ -309,14 +313,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - buttonClick = async (altKey: boolean, ctrlKey: boolean) => { - const linkDocs = DocListCast(this.props.Document.links); - if (linkDocs.length) { - DocumentManager.Instance.FollowLink(undefined, this.props.Document, - // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards - (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, maxLocation)), - ctrlKey, altKey, this.props.ContainingCollectionDoc); - } + // follows a link - if the target is on screen, it highlights/pans to it. + // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place + // depending on the followLinkLocation property of the source (or the link itself as a fallback); + followLinkClick = async (altKey: boolean, ctrlKey: boolean) => { + const batch = UndoManager.StartBatch("follow link click"); + const docFollowLoc = StrCast(this.Document.followLinkLocation); + const { focus, addDocTab } = this.props; + await DocumentManager.Instance.FollowLink(undefined, this.props.Document, + // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards (except if 'inPlace', then leave alone) + (doc, followLoc, afterFocus) => focus(this.props.Document, true, 1, () => { + addDocTab(doc, docFollowLoc || followLoc) && focus(doc, true, undefined, afterFocus); + return (docFollowLoc || followLoc) === "inPlace" ? false : true; + }), + ctrlKey, altKey, this.props.ContainingCollectionDoc, batch.end); } handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => { @@ -564,6 +574,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.Document.onClick = undefined; } else { this.Document.isButton = true; + this.Document.followLinkLocation = undefined; + } + } + + @undoBatch + toggleFollowInPlace = (): void => { + if (this.Document.isButton) { + this.Document.isButton = false; + } else { + this.Document.isButton = true; + this.Document.followLinkLocation = "inPlace"; } } @@ -693,6 +714,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); + onClicks.push({ description: this.Document.isButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" }); onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleButtonBehavior, icon: "concierge-bell" }); onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); @@ -981,31 +1003,36 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return this._link; } + hideLinkAnchor = (doc: Doc) => undoBatch(doc => doc.hidden = true)(); + anchorPanelWidth = () => this.props.PanelWidth() || 1; + anchorPanelHeight = () => this.props.PanelHeight() || 1; + @computed get anchors() { + TraceMobx(); + return DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => + <div className="documentView-docuLinkWrapper" key={d[Id]}> + <DocumentView {...this.props} + Document={d} + ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the DocuLinkBox + PanelWidth={this.anchorPanelWidth} + PanelHeight={this.anchorPanelHeight} + layoutKey={this.linkEndpoint(d)} + ContentScaling={returnOne} + backgroundColor={returnTransparent} + removeDocument={this.hideLinkAnchor} /> + </div>); + } @computed get innards() { TraceMobx(); - if (!this.props.PanelWidth()) { - return <div style={{ display: "flex", overflow: "hidden" }}> + if (!this.props.PanelWidth()) { // this happens when the document is a tree view label + return <div className="documentView-docuLinkAnchor" > {StrCast(this.props.Document.title)} - {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) => - <div className="documentView-docuLinkWrapper" style={{ position: "absolute", top: 0, left: 0 }} key={`${d[Id]}`}> - <DocumentView {...this.props} - Document={d} - ContainingCollectionDoc={this.props.Document} - PanelWidth={returnOne} PanelHeight={returnOne} - layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne} - backgroundColor={returnTransparent} - removeDocument={undoBatch(doc => doc.hidden = true)} /> - </div>)} + {this.anchors} </div>; } const showTitle = StrCast(this.layoutDoc._showTitle); const showTitleHover = StrCast(this.layoutDoc._showTitleHover); const showCaption = StrCast(this.layoutDoc._showCaption); const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined; - const searchHighlight = (!this.Document.searchFields ? (null) : - <div className="documentView-searchHighlight"> - {this.Document.searchFields} - </div>); const captionView = (!showCaption ? (null) : <div className="documentView-captionWrapper"> <DocumentContentsView {...OmitKeys(this.props, ['children']).omit} @@ -1032,26 +1059,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu /> </div>); return <> - {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) => - <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}> - <DocumentView {...this.props} ContentScaling={returnOne} ContainingCollectionDoc={this.props.Document} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} /> - </div>)} + {this.anchors} {!showTitle && !showCaption ? - this.Document.searchFields ? - (<div className="documentView-searchWrapper"> - {this.contents} - {searchHighlight} - </div>) - : - this.contents - : + this.contents : <div className="documentView-styleWrapper" > <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}> {this.contents} </div> {titleView} {captionView} - {searchHighlight} </div> } </>; diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index d1a563494..babca2a14 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -84,7 +84,7 @@ export class FormattedTextBoxComment { const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, - (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight")); + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight")); } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight"); } diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index d23c81065..5cb766c03 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -98,7 +98,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { const annoGroup = await Cast(this.props.document.group, Doc); if (annoGroup) { DocumentManager.Instance.FollowLink(undefined, annoGroup, - (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"), + (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"), false, false, undefined); e.stopPropagation(); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index c032f019d..25ceb6218 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -585,7 +585,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument dragComplete: e => { if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) { const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); - if (link) link.maximizeLocation = "onRight"; + if (link) link.followLinkLocation = "onRight"; } } }); diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index ed6b74ca8..dfd217e97 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -138,7 +138,7 @@ export default class MobileInterface extends React.Component { whenActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> + ContainingCollectionDoc={undefined} />; } return "hello"; } @@ -198,7 +198,7 @@ export default class MobileInterface extends React.Component { LibraryPath={emptyPath} fieldKey={""} dropAction={"alias"} - bringToFront={emptyFunction } + bringToFront={emptyFunction} addDocTab={returnFalse} pinToPres={emptyFunction} PanelHeight={() => window.innerHeight} diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 3ac46362a..1a5a471a0 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -46,12 +46,11 @@ export const documentSchema = createSchema({ treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) currentTimecode: "number", // current play back time of a temporal document (video / audio) - maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) + followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) lockedPosition: "boolean", // whether the document can be moved (dragged) lockedTransform: "boolean", // whether the document can be panned/zoomed inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently borderRounding: "string", // border radius rounding of document - searchFields: "string", // the search fields to display when this document matches a search in its metadata heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 8c719ccd8..480a55da0 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -12,7 +12,7 @@ function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); } -const tracing = false; +const tracing = true; export function TraceMobx() { tracing && trace(); } |