diff options
| author | brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> | 2023-01-19 14:33:22 -0500 | 
|---|---|---|
| committer | brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> | 2023-01-19 14:33:22 -0500 | 
| commit | 0ef7050b0792ce183c7d5cda637cb79b7a92b704 (patch) | |
| tree | d1dca8f09ddc2954c2ce88439172aeded672c0b6 /src/client/util/LinkFollower.ts | |
| parent | ceb338752aacc383c97a0e3a9b608365a1cf39b6 (diff) | |
| parent | d5f796b433d7e72130d4109a3775347ccb10c454 (diff) | |
Merge branch 'master' of github.com:brown-dash/Dash-Web into master
Diffstat (limited to 'src/client/util/LinkFollower.ts')
| -rw-r--r-- | src/client/util/LinkFollower.ts | 142 | 
1 files changed, 103 insertions, 39 deletions
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index f75ac24f5..0f216e349 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,12 +1,15 @@ -import { action, observable, observe } from 'mobx'; -import { computedFn } from 'mobx-utils'; -import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc'; -import { List } from '../../fields/List'; -import { ProxyField } from '../../fields/Proxy'; -import { BoolCast, Cast, StrCast } from '../../fields/Types'; +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 { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { PresBox } from '../views/nodes/trails';  import { DocumentManager } from './DocumentManager'; +import { LinkManager } from './LinkManager'; +import { SelectionManager } from './SelectionManager';  import { UndoManager } from './UndoManager';  type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; @@ -26,20 +29,20 @@ export class LinkFollower {      // 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, zoom: boolean = false) => { +    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 ? 'lightbox' : StrCast(sourceDoc.followLinkLocation, followLoc); +                    const where = LightboxView.LightboxDoc ? OpenWhere.inPlace : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere);                      docViewProps.addDocTab(doc, where);                      setTimeout(() => { -                        const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; -                        const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. +                        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, { -                                willZoom: BoolCast(sourceDoc.followLinkZoom, false), +                                willPan: true, +                                willPanZoom: BoolCast(sourceDoc.followLinkZoom, false),                                  afterFocus: (didFocus: boolean) => {                                      finished?.();                                      res(ViewAdjustment.resetView); @@ -47,63 +50,124 @@ export class LinkFollower {                                  },                              });                          } else { -                            res(where !== 'inPlace' ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace'  resetting the initial focus&zoom would negate the zoom into the target +                            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, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget }); +                docViewProps.focus(sourceDoc, { willPan: true, willPanZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomTime: 1000, zoomScale: 1, afterFocus: createTabForTarget });              }          }; -        LinkFollower.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); +        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 +        LinkFollower.traverseLink( +            linkDoc, +            sourceDoc, +            createViewFunc, +            docViewProps.ContainingCollectionDoc, +            action(() => { +                batch.end(); +                Doc.AddUnHighlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false))); +            }), +            altKey ? true : undefined +        );      }; -    public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { +    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).length === 0); -        const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); +        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 ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; -        const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1); +        const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; +        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 => { -            if (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 -                ) as Doc; -                if (target) { +            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 +            ) as Doc; +            if (target) { +                const doFollow = async (canToggle?: boolean) => { +                    const options: DocFocusOptions = { +                        playAudio: BoolCast(sourceDoc.followLinkAudio), +                        toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle), +                        willPan: true, +                        willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false), +                        zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500), +                        zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), +                        easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, +                        effect: sourceDoc, +                        originatingDoc: sourceDoc, +                        zoomTextSelections: false, +                    };                      if (target.TourMap) {                          const fieldKey = Doc.LayoutFieldKey(target);                          const tour = DocListCast(target[fieldKey]).reverse();                          LightboxView.SetLightboxDoc(currentContext, undefined, tour);                          setTimeout(LightboxView.Next);                          allFinished(); +                    } else if (target.type === DocumentType.PRES) { +                        const containerAnnoDoc = Cast(sourceDoc, Doc, null); +                        const containerDoc = containerAnnoDoc || sourceDoc; +                        var containerDocContext = containerDoc?.context ? [Cast(await containerDoc?.context, Doc, null)] : ([] as Doc[]); +                        while (containerDocContext.length && containerDocContext[0]?.context && DocCast(containerDocContext[0].context)?.viewType !== CollectionViewType.Docking) { +                            containerDocContext = [Cast(await containerDocContext[0].context, Doc, null), ...containerDocContext]; +                        } +                        if (!DocumentManager.Instance.getDocumentView(containerDocContext[0])) { +                            CollectionDockingView.AddSplit(containerDocContext[0], OpenWhereMod.right); +                        } +                        SelectionManager.DeselectAll(); +                        DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext[0])); +                        PresBox.OpenPresMinimized(target, [0, 0]); +                        finished?.();                      } else {                          const containerAnnoDoc = Cast(target.annotationOn, Doc, null);                          const containerDoc = containerAnnoDoc || target; -                        var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]); -                        while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { -                            containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; +                        var containerDocContext = containerDoc?.context ? [Cast(await containerDoc?.context, Doc, null)] : ([] as Doc[]); +                        while (containerDocContext.length && containerDocContext[0]?.context && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && DocCast(containerDocContext[0].context)?.viewType !== CollectionViewType.Docking) { +                            containerDocContext = [Cast(await containerDocContext[0].context, Doc, null), ...containerDocContext];                          }                          const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; -                        DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'lightbox'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); +                        DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, 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); +                        movedTarget = true; +                    } +                    if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) { +                        Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target); +                        movedTarget = true; +                    } +                    target.context = 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]; +                        movedTarget = true; +                    } +                    if (sourceDoc.followLinkYoffset !== undefined && moveTo[1] !== target.y) { +                        target.y = moveTo[1]; +                        movedTarget = true;                      } -                } else { -                    allFinished(); -                } +                    if (movedTarget) setTimeout(doFollow); +                    else doFollow(true); +                } else doFollow(true);              } else {                  allFinished();              }  | 
