aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/LinkFollower.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/LinkFollower.ts')
-rw-r--r--src/client/util/LinkFollower.ts135
1 files changed, 57 insertions, 78 deletions
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 57618b53c..f74409e42 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,22 +1,19 @@
-import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { DocumentDecorations } from '../views/DocumentDecorations';
-import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
+import { action, observable, runInAction } from 'mobx';
+import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
+import { ScriptField } from '../../fields/ScriptField';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { DocumentType } from '../documents/DocumentTypes';
+import { DocFocusOptions, OpenWhere } from '../views/nodes/DocumentView';
import { PresBox } from '../views/nodes/trails';
import { DocumentManager } from './DocumentManager';
import { LinkManager } from './LinkManager';
+import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
import { UndoManager } from './UndoManager';
-
-type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
/*
* link doc:
- * - anchor1: doc
- * - anchor2: doc
+ * - link_anchor_1: doc
+ * - link_anchor_2: doc
*
* group doc:
* - type: string representing the group type/name/category
@@ -26,117 +23,87 @@ type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => vo
* - user defined kvps
*/
export class LinkFollower {
+ @observable public static IsFollowing = false;
// 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 the lightbox, or in place
// depending on the followLinkLocation property of the source (or the link itself as a fallback);
- public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => {
- const batch = UndoManager.StartBatch('follow link click');
- // open up target if it's not already in view ...
- const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
- const createTabForTarget = (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- const where = LightboxView.LightboxDoc ? OpenWhere.inPlace : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere);
- docViewProps.addDocTab(doc, where);
- setTimeout(() => {
- const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
- if (targDocView) {
- targDocView.props.focus(doc, {
- willPan: true,
- willPanZoom: BoolCast(sourceDoc.followLinkZoom, false),
- afterFocus: (didFocus: boolean) => {
- finished?.();
- res(ViewAdjustment.resetView);
- return new Promise<ViewAdjustment>(res2 => res2(ViewAdjustment.doNothing));
- },
- });
- } else {
- finished?.();
- res(where !== OpenWhere.inPlace || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target
- }
- }, 100);
- });
-
- if (!sourceDoc.followLinkZoom) {
- createTabForTarget(false);
- } else {
- // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- docViewProps.focus(sourceDoc, { willPan: true, willPanZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomTime: 1000, zoomScale: 1, afterFocus: createTabForTarget });
- }
- };
- runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
+ public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, altKey: boolean) => {
+ const batch = UndoManager.StartBatch('Follow Link');
+ runInAction(() => (LinkFollower.IsFollowing = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
LinkFollower.traverseLink(
linkDoc,
sourceDoc,
- createViewFunc,
- docViewProps.ContainingCollectionDoc,
action(() => {
batch.end();
- Doc.AddUnHighlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false)));
+ Doc.AddUnHighlightWatcher(action(() => (LinkFollower.IsFollowing = false)));
}),
altKey ? true : undefined
);
};
- public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
- const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
- const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
- const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor2 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor2 as Doc).annotationOn) : (d.anchor2 as Doc)).length === 0);
- const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor1 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor1 as Doc).annotationOn) : (d.anchor1 as Doc)).length === 0);
- const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
- const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs;
+ public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
+ const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
+ const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc);
+ const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
+ const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
+ const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
+ const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2)));
+ const backLinkWithoutTargetView = backLinks.find(l => !getView(DocCast(l.link_anchor_1)));
+ const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView ?? backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
+ const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
var count = 0;
const allFinished = () => ++count === followLinks.length && finished?.();
if (!followLinks.length) finished?.();
followLinks.forEach(async linkDoc => {
const target = (
- sourceDoc === linkDoc.anchor1
- ? linkDoc.anchor2
- : sourceDoc === linkDoc.anchor2
- ? linkDoc.anchor1
- : Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)
- ? linkDoc.anchor2
- : linkDoc.anchor1
+ sourceDoc === linkDoc.link_anchor_1
+ ? linkDoc.link_anchor_2
+ : sourceDoc === linkDoc.link_anchor_2
+ ? linkDoc.link_anchor_1
+ : Doc.AreProtosEqual(sourceDoc, linkDoc.link_anchor_1 as Doc) || Doc.AreProtosEqual((linkDoc.link_anchor_1 as Doc).annotationOn as Doc, sourceDoc)
+ ? linkDoc.link_anchor_2
+ : linkDoc.link_anchor_1
) as Doc;
if (target) {
const doFollow = (canToggle?: boolean) => {
+ const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
const options: DocFocusOptions = {
playAudio: BoolCast(sourceDoc.followLinkAudio),
- toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle),
+ toggleTarget,
+ noSelect: true,
willPan: true,
- willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false),
- zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500),
+ willZoomCentered: BoolCast(sourceDoc.followLinkZoom, false),
+ zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500),
zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
+ openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere,
effect: sourceDoc,
- originatingDoc: sourceDoc,
zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
};
if (target.type === DocumentType.PRES) {
const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
SelectionManager.DeselectAll();
- DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext));
- PresBox.OpenPresMinimized(target, [0, 0]);
+ if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext))) {
+ PresBox.OpenPresMinimized(target, [0, 0]);
+ }
finished?.();
} else {
- const containerDocContext = DocumentManager.GetContextPath(target);
- const targetContexts = !sourceDoc.followLinkToOuterContext && containerDocContext.length ? [containerDocContext.lastElement()] : containerDocContext;
- DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished);
+ DocumentManager.Instance.showDocument(target, options, allFinished);
}
};
let movedTarget = false;
if (sourceDoc.followLinkLocation === OpenWhere.inParent) {
- const sourceDocParent = DocCast(sourceDoc.context);
- if (target.context instanceof Doc && target.context !== sourceDocParent) {
- Doc.RemoveDocFromList(target.context, Doc.LayoutFieldKey(target.context), target);
+ const sourceDocParent = DocCast(sourceDoc.embedContainer);
+ if (target.embedContainer instanceof Doc && target.embedContainer !== sourceDocParent) {
+ Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutFieldKey(target.embedContainer), target);
movedTarget = true;
}
if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) {
Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
movedTarget = true;
}
- target.context = sourceDocParent;
+ target.embedContainer = sourceDocParent;
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
target.x = moveTo[0];
@@ -155,3 +122,15 @@ export class LinkFollower {
});
}
}
+
+ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
+ SelectionManager.DeselectAll();
+ LinkFollower.FollowLink(undefined, doc, altKey);
+});
+
+export function FollowLinkScript() {
+ return ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
+}
+export function IsFollowLinkScript(field: FieldResult<Field>) {
+ return ScriptCast(field)?.script.originalScript.includes('followLink(');
+}