diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/DocumentManager.ts | 157 | ||||
| -rw-r--r-- | src/client/util/LinkManager.ts | 61 |
2 files changed, 117 insertions, 101 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 41c7a1409..816f7f6be 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,13 +1,13 @@ -import { action, observable } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { Cast, NumCast } from '../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; +import { LightboxView } from '../views/LightboxView'; import { DocumentView } from '../views/nodes/DocumentView'; -import { LinkManager } from './LinkManager'; import { Scripting } from './Scripting'; export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; @@ -15,32 +15,28 @@ export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: ( export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) - @observable - public DocumentViews: DocumentView[] = []; - @observable LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = []; + @observable public DocumentViews: DocumentView[] = []; + @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = []; - // singleton instance private static _instance: DocumentManager; - - // create one and only one instance of NodeManager - public static get Instance(): DocumentManager { - return this._instance || (this._instance = new this()); - } + public static get Instance(): DocumentManager { return this._instance || (this._instance = new this()); } //private constructor so no other class can create a nodemanager - private constructor() { - } + private constructor() { } @action public AddView = (view: DocumentView) => { - const linksList = DocListCast(view.props.Document.links); - linksList.forEach(link => { - const linkToDoc = link && LinkManager.getOppositeAnchor(link, view.props.Document); - linkToDoc && DocumentManager.Instance.DocumentViews.filter(dv => Doc.AreProtosEqual(dv.props.Document, linkToDoc)).forEach(dv => { - if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== view.props.LayoutTemplateString) { - this.LinkedDocumentViews.push({ a: dv, b: view, l: link }); - } - }); + DocListCast(view.rootDoc.links).forEach(link => { + const whichOtherAnchor = view.props.LayoutTemplateString?.includes("anchor2") ? "anchor1" : "anchor2"; + const otherDoc = link && (link[whichOtherAnchor] as Doc); + const otherDocAnno = otherDoc?.type === DocumentType.TEXTANCHOR ? otherDoc.annotationOn as Doc : undefined; + otherDoc && DocumentManager.Instance.DocumentViews. + filter(dv => Doc.AreProtosEqual(dv.rootDoc, otherDoc) || Doc.AreProtosEqual(dv.rootDoc, otherDocAnno)). + forEach(otherView => { + if (otherView.rootDoc.type !== DocumentType.LINK || otherView.props.LayoutTemplateString !== view.props.LayoutTemplateString) { + this.LinkedDocumentViews.push({ a: whichOtherAnchor === "anchor1" ? otherView : view, b: whichOtherAnchor === "anchor1" ? view : otherView, l: link }); + } + }); }); this.DocumentViews.push(view); } @@ -55,13 +51,13 @@ export class DocumentManager { public getDocumentViewsById(id: string) { const toReturn: DocumentView[] = []; DocumentManager.Instance.DocumentViews.map(view => { - if (view.props.Document[Id] === id) { + if (view.rootDoc[Id] === id) { toReturn.push(view); } }); if (toReturn.length === 0) { DocumentManager.Instance.DocumentViews.map(view => { - const doc = view.props.Document.proto; + const doc = view.rootDoc.proto; if (doc && doc[Id] && doc[Id] === id) { toReturn.push(view); } @@ -81,14 +77,14 @@ export class DocumentManager { for (const pass of passes) { DocumentManager.Instance.DocumentViews.map(view => { - if (view.props.Document[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { + if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { toReturn = view; return; } }); if (!toReturn) { DocumentManager.Instance.DocumentViews.map(view => { - const doc = view.props.Document.proto; + const doc = view.rootDoc.proto; if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { toReturn = view; } @@ -105,21 +101,28 @@ export class DocumentManager { return this.getDocumentViewById(toFind[Id], preferredCollection); } + public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => { + const docViews = DocumentManager.Instance.DocumentViews; + const views: DocumentView[] = []; + docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && view.rootDoc === toFind && views.push(view)); + return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); + } public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => { - const views = this.getDocumentViews(toFind).filter(view => view.props.Document !== originatingDoc); + const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); } public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; - const docViews = DocumentManager.Instance.DocumentViews; + const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath)); + const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath)); // heuristic to return the "best" documents first: + // choose a document in the lightbox 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) - docViews.map(view => view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view)); - docViews.map(view => view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view)); - docViews.map(view => view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); - docViews.map(view => view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + lightViews.map(view => view.rootDoc === toFind && toReturn.push(view)); + lightViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view)); + docViews.map(view => view.rootDoc === toFind && toReturn.push(view)); + docViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view)); return toReturn; } @@ -139,56 +142,51 @@ export class DocumentManager { originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc finished?: () => void, ): Promise<void> => { - const getFirstDocView = DocumentManager.Instance.getFirstDocumentView; - const focusAndFinish = () => { finished?.(); return false; }; + const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; + const docView = getFirstDocView(targetDoc, originatingDoc); const highlight = () => { const finalDocView = getFirstDocView(targetDoc); - if (finalDocView) { - finalDocView.layoutDoc.scrollToLinkID = linkDoc?.[Id]; - Doc.linkFollowHighlight(finalDocView.props.Document); + finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc); + }; + const focusAndFinish = (didFocus: boolean) => { + if (originatingDoc?.isPushpin) { + if (!didFocus || targetDoc.hidden) { + targetDoc.hidden = !targetDoc.hidden; + } + } else { + targetDoc.hidden && (targetDoc.hidden = undefined); + docView?.select(false); } + highlight(); + finished?.(); + return false; }; - const docView = getFirstDocView(targetDoc, originatingDoc); - let annotatedDoc = await Cast(targetDoc.annotationOn, Doc); - if (annotatedDoc && annotatedDoc !== originatingDoc?.context && !targetDoc?.isPushpin) { + let annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); + const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; + const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined; + const targetDocContext = contextDoc || annotatedDoc; + const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext); + if (!docView && annotatedDoc && annotatedDoc !== originatingDoc?.context && targetDoc.type === DocumentType.TEXTANCHOR) { const first = getFirstDocView(annotatedDoc); if (first) { - annotatedDoc = first.props.Document; - first.props.focus(annotatedDoc, false); + annotatedDoc = first.rootDoc; + first.focus(targetDoc, false); } - } - 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? - const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context; - if (originatingDoc?.isPushpin) { - docView.props.focus(docView.props.Document, willZoom, undefined, (didFocus: boolean) => { - if (!didFocus || docView.props.Document.hidden) { - docView.props.Document.hidden = !docView.props.Document.hidden; - } - return focusAndFinish(); - }, sameContext, false);// don't want to focus the container if the source and target are in the same container, so pass 'sameContext' for dontCenter parameter - //finished?.(); - } - else { - docView.select(false); - docView.props.Document.hidden && (docView.props.Document.hidden = undefined); - // @ts-ignore - docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish, sameContext, false); - } - highlight(); + } else if (docView && (targetDocContextView || !targetDocContext)) { // 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.rootDoc, willZoom, undefined, (didFocus: boolean) => + new Promise<boolean>(res => { + focusAndFinish(didFocus); + res(); + }) + ); } else { - const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; - 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 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); highlight(); } else { // otherwise try to get a view of the context of the target - const targetDocContextView = getFirstDocView(targetDocContext); - targetDocContext._scrollY = targetDocContext._scrollPreviewY = NumCast(targetDocContext._scrollTop, 0); // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext._viewTransition = "transform 500ms"; - targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); + targetDocContextView.props.focus(targetDocContextView.rootDoc, willZoom); // now find the target document within the context if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; @@ -198,12 +196,17 @@ export class DocumentManager { const findView = (delay: number) => { const retryDocView = getFirstDocView(targetDoc); // test again for the target view snce we presumably created the context above by focusing on it if (retryDocView) { // we found the target in the context - retryDocView.props.focus(targetDoc, willZoom, undefined, focusAndFinish); // focus on the target in the context + retryDocView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) => + new Promise<boolean>(res => { + focusAndFinish(didFocus); + res(); + }) + ); // focus on the target in the context highlight(); } else if (delay > 1500) { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. - if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document); - if (targetDoc.layout) { + if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); + if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document Doc.SetInPlace(targetDoc, "annotationOn", undefined, false); createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target } @@ -214,8 +217,14 @@ export class DocumentManager { findView(0); } } else { // there's no context view so we need to create one first and try again when that finishes - createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished)); + const finishFunc = () => this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished); + if (LightboxView.LightboxDoc) { + runInAction(() => LightboxView.LightboxDoc = targetDocContext); + setTimeout(() => finishFunc, 250); + } else { + createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target + finishFunc); + } } } } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index bf927c5b8..58ccfe645 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,9 +1,9 @@ +import { runInAction } from "mobx"; import { computedFn } from "mobx-utils"; import { Doc, DocListCast, Opt } from "../../fields/Doc"; import { BoolCast, Cast, StrCast } from "../../fields/Types"; -import { DocFocusFunc, DocumentViewSharedProps } from "../views/nodes/DocumentView"; -import { FormattedTextBoxComment } from "../views/nodes/formattedText/FormattedTextBoxComment"; -import { LinkDocPreview } from "../views/nodes/LinkDocPreview"; +import { LightboxView } from "../views/LightboxView"; +import { DocumentViewSharedProps } from "../views/nodes/DocumentView"; import { CreateViewFunc, DocumentManager } from "./DocumentManager"; import { SharingManager } from "./SharingManager"; import { UndoManager } from "./UndoManager"; @@ -100,35 +100,37 @@ export class LinkManager { // 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); - public static FollowLink = async (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => { + 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 targetFocusAfterDocFocus = () => { + const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { + const createTabForTarget = (didFocus: boolean) => new Promise<boolean>(res => { const where = StrCast(sourceDoc.followLinkLocation) || followLoc; - const hackToCallFinishAfterFocus = () => { - finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout. - return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false - }; - const addTab = docViewProps.addDocTab(doc, where); - addTab && setTimeout(() => { + docViewProps.addDocTab(doc, where); + setTimeout(() => { const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); - targDocView?.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, hackToCallFinishAfterFocus); - }); // add the target and focus on it. - return where !== "inPlace" || addTab; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target) - }; + if (targDocView) { + targDocView.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, (didFocus: boolean) => { + finished?.(); + res(true); + return new Promise<boolean>(res2 => res2()); + }); + } else { + res(where !== "inPlace"); // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target) + } + }); + }); + if (!sourceDoc.followLinkZoom) { - targetFocusAfterDocFocus(); + createTabForTarget(false); } else { // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target - docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, targetFocusAfterDocFocus); + docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, createTabForTarget); } }; - await LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); + LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); } - public static async traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { - LinkDocPreview.TargetDoc = undefined; - FormattedTextBoxComment.linkDoc = undefined; + public static traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(doc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2 @@ -145,11 +147,16 @@ export class LinkManager { doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") : (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); if (target) { - const containerDoc = (await Cast(target.annotationOn, Doc)) || target; - containerDoc._currentTimecode = targetTimecode; - const targetContext = await target?.context as Doc; - const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished); + if (LightboxView.LightboxDoc && !DocumentManager.Instance.getLightboxDocumentView(doc)) { + runInAction(() => LightboxView.LightboxDoc = (target.annotationOn as Doc) ?? target); + finished?.(); + } else { + const containerDoc = Cast(target.annotationOn, Doc, null) || target; + targetTimecode !== undefined && (containerDoc._currentTimecode = targetTimecode); + const targetContext = Cast(containerDoc?.context, Doc, null); + const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished); + } } else { finished?.(); } |
