aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/DocumentManager.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/DocumentManager.ts')
-rw-r--r--src/client/util/DocumentManager.ts240
1 files changed, 120 insertions, 120 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 40d28c690..8ad6ddf47 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,35 +1,30 @@
import { Howl } from 'howler';
import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
-import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
+import { Animation, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
-import { GetEffectiveAcl } from '../../fields/util';
import { CollectionViewType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { TabDocView } from '../views/collections/TabDocView';
-import { LightboxView } from '../views/LightboxView';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
-import { KeyValueBox } from '../views/nodes/KeyValueBox';
-import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { PresBox } from '../views/nodes/trails';
-import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
+type childIterator = { viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean; contextPath: Doc[] };
export class DocumentManager {
+ // eslint-disable-next-line no-use-before-define
private static _instance: DocumentManager;
public static get Instance(): DocumentManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
- //global holds all of the nodes (regardless of which collection they're in)
- @observable _documentViews = new Set<DocumentView>();
- @observable.shallow public CurrentlyLoading: Doc[] = [];
+ // global holds all of the nodes (regardless of which collection they're in)
+ @observable private _documentViews = new Set<DocumentView>();
@computed public get DocumentViews() {
- return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
+ return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !DocumentView.LightboxDoc()) || DocumentView.LightboxContains(view));
}
public AddDocumentView(dv: DocumentView) {
this._documentViews.add(dv);
@@ -38,10 +33,22 @@ export class DocumentManager {
this._documentViews.delete(dv);
}
- //private constructor so no other class can create a nodemanager
+ // private constructor so no other class can create a nodemanager
private constructor() {
makeObservable(this);
- observe(this.CurrentlyLoading, change => {
+
+ DocumentView.allViews = () => this.DocumentViews;
+ DocumentView.addView = this.AddView;
+ DocumentView.removeView = this.RemoveView;
+ DocumentView.showDocument = this.showDocument;
+ DocumentView.showDocumentView = this.showDocumentView;
+ DocumentView.linkCommonAncestor = DocumentManager.LinkCommonAncestor;
+ DocumentView.addViewRenderedCb = this.AddViewRenderedCb;
+ DocumentView.getFirstDocumentView = this.getFirstDocumentView;
+ DocumentView.getDocumentView = this.getDocumentView;
+ DocumentView.getContextPath = DocumentManager.GetContextPath;
+ DocumentView.getLightboxDocumentView = this.getLightboxDocumentView;
+ observe(Doc.CurrentlyLoading, change => {
// watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
switch (change.type as any) {
case 'update':
@@ -52,6 +59,7 @@ export class DocumentManager {
case 'splice':
(change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
break;
+ default:
}
});
}
@@ -59,7 +67,7 @@ export class DocumentManager {
private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
if (doc) {
- const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
+ const dv = DocumentView.LightboxDoc() ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
this._viewRenderedCbs.push({ doc, func });
if (dv) {
this.callAddViewFuncs(dv);
@@ -88,20 +96,15 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) &&
- !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- this.AddDocumentView(view);
- this.callAddViewFuncs(view);
- } // prettier-ignore
+ this.AddDocumentView(view);
+ this.callAddViewFuncs(view);
};
public RemoveView = action((view: DocumentView) => {
- if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- this.DeleteDocumentView(view);
- }
- SelectionManager.DeselectView(view);
+ this.DeleteDocumentView(view);
+ DocumentView.DeselectView(view);
});
- //gets all views
+ // gets all views
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => {
@@ -135,28 +138,20 @@ export class DocumentManager {
);
}
- public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+ public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => {
const views: DocumentView[] = [];
- DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, 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);
+ DocumentManager.Instance.DocumentViews.forEach(view => DocumentView.LightboxContains(view) && Doc.AreProtosEqual(view.Document, 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 => {
- if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc);
- const views = this.getDocumentViews(toFind); //.filter(view => view.Document !== 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 getFirstDocumentView = (toFind: Doc): DocumentView | undefined => {
+ if (DocumentView.LightboxDoc()) return DocumentManager.Instance.getLightboxDocumentView(toFind);
+ const views = this.getDocumentViews(toFind); // .filter(view => view.Document !== 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(toFindIn: Doc): DocumentView[] {
- const toFind =
- // Array.from(DocumentManager.Instance.DocumentViews).find(
- // dv =>
- // ((dv.Document.data as any)?.url?.href && (dv.Document.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
- // ((DocCast(dv.Document.annotationOn)?.data as any)?.url?.href && (DocCast(dv.Document.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
- // )?.Document ??
- toFindIn;
-
+ public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
- const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view));
- const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view));
+ const docViews = DocumentManager.Instance.DocumentViews.filter(view => !DocumentView.LightboxContains(view));
+ const lightViews = DocumentManager.Instance.DocumentViews.filter(view => DocumentView.LightboxContains(view));
// heuristic to return the "best" documents first:
// choose a document in the lightbox first
@@ -172,7 +167,7 @@ export class DocumentManager {
static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
if (!doc) return [];
const srcContext = DocCast(doc.annotationOn, DocCast(doc.embedContainer));
- var containerDocContext = srcContext ? [srcContext, doc] : [doc];
+ let containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
DocCast(containerDocContext[0]?.embedContainer) &&
@@ -184,11 +179,13 @@ export class DocumentManager {
return containerDocContext;
}
+ static _howl: Howl;
static playAudioAnno(doc: Doc) {
const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
+ this._howl?.stop();
if (anno instanceof AudioField) {
- new Howl({
+ this._howl = new Howl({
src: [anno.url.href],
format: ['mp3'],
autoplay: true,
@@ -204,19 +201,20 @@ export class DocumentManager {
DocumentManager._overlayViews?.clear();
}
static _overlayViews = new ObservableSet<DocumentView>();
- static addView = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- finished?.();
- };
+ /**
+ * Find the nearest common ancestor collection that contains a link's source and target
+ * @param linkDoc
+ * @returns common ancestor DocumentView
+ */
public static LinkCommonAncestor(linkDoc: Doc) {
- const anchor = (which: number) => {
+ const getAnchor = (which: number) => {
const anch = DocCast(linkDoc['link_anchor_' + which]);
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
return DocumentManager.Instance.getDocumentView(anchor);
};
- const anchor1 = anchor(1);
- const anchor2 = anchor(2);
+ const anchor1 = getAnchor(1);
+ const anchor2 = getAnchor(2);
return anchor1
?.docViewPath()
.reverse()
@@ -228,8 +226,10 @@ export class DocumentManager {
// focusing on each context
public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
- let rootContextView = docViewPath.shift();
- await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
+ const rootContextView = docViewPath.shift();
+ const iterator = () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false, contextPath: docViewPath.map(dv => dv.Document) });
+ options.contextPath = docViewPath.map(dv => dv.Document);
+ await (rootContextView && this.focusViewsInPath(rootContextView, options, iterator));
if (options.toggleTarget && (!options.didMove || targetDocView.Document.hidden)) targetDocView.Document.hidden = !targetDocView.Document.hidden;
else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.Document, options.openLocation);
};
@@ -242,85 +242,111 @@ export class DocumentManager {
// and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
public showDocument = async (
targetDoc: Doc, // document to display
- options: FocusViewOptions, // options for how to navigate to target
+ optionsIn: FocusViewOptions, // options for how to navigate to target
finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
+ const options = optionsIn;
Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
- const tabView = Array.from(TabDocView._allTabs).find(view => view._document === docContextPath[0]);
- if (!tabView?._activated && tabView?._document) {
- options.toggleTarget = false;
- TabDocView.Activate(tabView?._document);
- }
- let rootContextView =
+ if (DocumentView.activateTabView(docContextPath[0])) options.toggleTarget = false;
+
+ const rootContextView =
docContextPath.length &&
(await new Promise<DocumentView>(res => {
const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
if (viewIndex !== -1) {
viewIndex && docContextPath.splice(0, viewIndex);
- return res(this.getDocumentView(docContextPath[0])!);
+ res(this.getDocumentView(docContextPath[0])!);
+ return;
}
options.didMove = true;
- (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ (!DocumentView.LightboxDoc() && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
}));
- if (options.openLocation === OpenWhere.lightbox) {
+ if (options.openLocation?.includes(OpenWhere.lightbox)) {
// even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
const target = DocCast(targetDoc.annotationOn, targetDoc);
const contextView = this.getDocumentView(DocCast(target.embedContainer));
- if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
- await new Promise<void>(waitres => setTimeout(() => waitres()));
+ if (contextView?.ComponentView?.addDocTab?.(target, options.openLocation)) {
+ await new Promise<void>(waitres => {
+ setTimeout(() => waitres());
+ });
}
}
- docContextPath.shift();
- const childViewIterator = async (docView: DocumentView) => {
- const innerDoc = docContextPath.shift();
- return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc) : undefined };
- };
if (rootContextView) {
+ const childViewIterator = async (docView: DocumentView): Promise<childIterator> => {
+ const innerDoc = docContextPath.shift();
+ const childDocView = innerDoc && !innerDoc.layout_unrendered
+ ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc):
+ undefined; // prettier-ignore
+ return { focused: false, viewSpec: innerDoc, childDocView, contextPath: docContextPath };
+ };
+ docContextPath.shift();
+ options.contextPath = docContextPath;
const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
- this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- finished?.(target.focused);
- } else finished?.(false);
+ if (target) {
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ return;
+ }
+ }
+ finished?.(false);
};
focusViewsInPath = async (
- docView: DocumentView, //
- options: FocusViewOptions,
- iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
+ docViewIn: DocumentView, //
+ optionsIn: FocusViewOptions,
+ iterator: (docView: DocumentView) => childIterator | Promise<childIterator>
) => {
let contextView: DocumentView | undefined; // view containing context that contains target
let focused = false;
- while (true) {
+ let docView = docViewIn;
+ let anchor = docView.Document;
+ const options = optionsIn;
+ const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop
+ for (let i = 0; i < maxFocusLength; i++) {
if (docView.Document.layout_fieldKey === 'layout_icon') {
- await new Promise<void>(res => docView.iconify(res));
+ // eslint-disable-next-line no-loop-func
+ const prom = new Promise<void>(res => {
+ docView.iconify(res);
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await prom;
options.didMove = true;
}
- const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container
- focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
- const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
- contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
+ const nextFocus = docView._props.focus(anchor, options); // focus the view within its container
+ focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
+ // eslint-disable-next-line no-await-in-loop
+ const { childDocView, viewSpec, contextPath } = await iterator(docView);
+ if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused };
+ contextView = !childDocView.Document.layout_unrendered ? childDocView : docView;
docView = childDocView;
+ anchor = viewSpec ?? docView.Document;
+ options.contextPath = contextPath;
}
+ options.contextPath = undefined;
+ return undefined;
};
@action
- restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ restoreDocView(viewSpec: Opt<Doc>, docViewIn: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ const docView = docViewIn;
if (viewSpec && docView) {
- //if (docView.ComponentView instanceof FormattedTextBox)
- //viewSpec !== docView.Document &&
+ // if (docView.ComponentView instanceof FormattedTextBox)
+ // viewSpec !== docView.Document &&
docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
- Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
+ // if there's an options.effect, it will be handled from linkFollowHighlight. We delay the start of
+ // the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen
+ // bcz: should this delay be an options parameter?
+ setTimeout(() => Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect), (options.zoomTime ?? 0) * 0.5);
if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode));
if (options.playAudio) DocumentManager.playAudioAnno(docView.Document);
if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden;
- if (options.effect) docView.Document[Animation] = options.effect;
- if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) {
+ if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
DocumentManager._overlayViews.add(contextView);
@@ -332,30 +358,4 @@ export class DocumentManager {
}
}
}
-export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
- const func = () => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
- DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
- } else {
- const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
- const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
- options.toggleTarget = undefined;
- DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- dv && Doc.linkFollowHighlight(dv.Document);
- });
- }
- };
- if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
- doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
- }
- if (doc.hidden) {
- doc.hidden = false;
- options.toggleTarget = false;
- setTimeout(func);
- } else func();
-}
-ScriptingGlobals.add(DocFocusOrOpen);
+setTimeout(() => DocumentManager.Instance);