aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/LinkManager.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/LinkManager.ts')
-rw-r--r--src/client/util/LinkManager.ts118
1 files changed, 78 insertions, 40 deletions
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 0512864df..3c3d5c3b8 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,22 +1,21 @@
-import { runInAction } from "mobx";
import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, Opt } from "../../fields/Doc";
-import { BoolCast, Cast, StrCast } from "../../fields/Types";
+import { Doc, DocListCast, Opt, DirectLinksSym, Field } from "../../fields/Doc";
+import { BoolCast, Cast, StrCast, PromiseValue } from "../../fields/Types";
import { LightboxView } from "../views/LightboxView";
import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
import { DocumentManager } from "./DocumentManager";
import { SharingManager } from "./SharingManager";
import { UndoManager } from "./UndoManager";
+import { observe, observable, reaction } from "mobx";
+import { listSpec } from "../../fields/Schema";
+import { List } from "../../fields/List";
+import { ProxyField } from "../../fields/Proxy";
type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
/*
* link doc:
- * - anchor1: doc
- * - anchor1page: number
- * - anchor1groups: list of group docs representing the groups anchor1 categorizes this link/anchor2 in
+ * - anchor1: doc
* - anchor2: doc
- * - anchor2page: number
- * - anchor2groups: list of group docs representing the groups anchor2 categorizes this link/anchor1 in
*
* group doc:
* - type: string representing the group type/name/category
@@ -27,38 +26,84 @@ type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => vo
*/
export class LinkManager {
- private static _instance: LinkManager;
+ @observable static _instance: LinkManager;
+ @observable static userDocs: Doc[] = [];
public static currentLink: Opt<Doc>;
- public static get Instance(): LinkManager { return this._instance || (this._instance = new this()); }
+ public static get Instance() { return LinkManager._instance; }
+ constructor() {
+ LinkManager._instance = this;
+ setTimeout(() => {
+ LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
+ const addLinkToDoc = (link: Doc): any => {
+ const a1 = link?.anchor1;
+ const a2 = link?.anchor2;
+ if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => addLinkToDoc(link)));
+ if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+ Doc.GetProto(a1)[DirectLinksSym].add(link);
+ Doc.GetProto(a2)[DirectLinksSym].add(link);
+ Doc.GetProto(link)[DirectLinksSym].add(link);
+ }
+ };
+ const remLinkFromDoc = (link: Doc): any => {
+ const a1 = link?.anchor1;
+ const a2 = link?.anchor2;
+ if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => remLinkFromDoc(link)));
+ if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+ Doc.GetProto(a1)[DirectLinksSym].delete(link);
+ Doc.GetProto(a2)[DirectLinksSym].delete(link);
+ Doc.GetProto(link)[DirectLinksSym].delete(link);
+ }
+ };
+ const watchUserLinks = (userLinks: List<Doc>) => {
+ const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
+ observe(userLinks, change => {
+ switch (change.type as any) {
+ case "splice":
+ (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
+ (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
+ break;
+ case "update": //let oldValue = change.oldValue;
+ }
+ }, true);
+ };
+ observe(LinkManager.userDocs, change => {
+ switch (change.type as any) {
+ case "splice": (change as any).added.forEach(watchUserLinks); break;
+ case "update": //let oldValue = change.oldValue;
+ }
+ }, true);
+ });
+ }
- public addLink(linkDoc: Doc) { return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc); }
+ public addLink(linkDoc: Doc) {
+ return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+ }
public deleteLink(linkDoc: Doc) { return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); }
public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
- public getAllDirectLinks(anchor: Doc): Doc[] { return this.directLinker(anchor); } // finds all links that contain the given anchor
- public getAllLinks(): Doc[] { return this.allLinks(); }
+ public getAllDirectLinks(anchor: Doc): Doc[] {
+ return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
+ } // finds all links that contain the given anchor
+ public getAllLinks(): Doc[] { return []; }//this.allLinks(); }
- allLinks = computedFn(function allLinks(this: any): Doc[] {
- const linkData = Doc.LinkDBDoc().data;
- const lset = new Set<Doc>(DocListCast(linkData));
- SharingManager.Instance.users.forEach(user => DocListCast(user.linkDatabase?.data).forEach(doc => lset.add(doc)));
- return Array.from(lset);
- }, true);
-
- directLinker = computedFn(function directLinker(this: any, anchor: Doc): Doc[] {
- return LinkManager.Instance.allLinks().filter(link => {
- const a1 = Cast(link?.anchor1, Doc, null);
- const a2 = Cast(link?.anchor2, Doc, null);
- return link && ((a1?.author !== undefined && a2?.author !== undefined) || link.author === Doc.CurrentUserEmail) && (Doc.AreProtosEqual(anchor, a1) || Doc.AreProtosEqual(anchor, a2) || Doc.AreProtosEqual(link, anchor));
- });
- }, true);
+ // allLinks = computedFn(function allLinks(this: any): Doc[] {
+ // const linkData = Doc.LinkDBDoc().data;
+ // const lset = new Set<Doc>(DocListCast(linkData));
+ // SharingManager.Instance.users.forEach(user => DocListCast(user.linkDatabase?.data).forEach(doc => lset.add(doc)));
+ // LinkManager.Instance.allLinks().filter(link => {
+ // const a1 = Cast(link?.anchor1, Doc, null);
+ // const a2 = Cast(link?.anchor2, Doc, null);
+ // return link && ((a1?.author !== undefined && a2?.author !== undefined) || link.author === Doc.CurrentUserEmail) && (Doc.AreProtosEqual(anchor, a1) || Doc.AreProtosEqual(anchor, a2) || Doc.AreProtosEqual(link, anchor));
+ // });
+ // return Array.from(lset);
+ // }, true);
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
const lfield = Doc.LayoutFieldKey(anchor);
return DocListCast(anchor[lfield + "-annotations"]).concat(DocListCast(anchor[lfield + "-annotations-timeline"])).reduce((list, anno) =>
[...list, ...LinkManager.Instance.relatedLinker(anno)],
- LinkManager.Instance.directLinker(anchor).slice());
+ Array.from(Doc.GetProto(anchor)[DirectLinksSym]).slice());// LinkManager.Instance.directLinker(anchor).slice());
}, true);
// returns map of group type to anchor's links in that group type
@@ -77,13 +122,6 @@ export class LinkManager {
return anchorGroups;
}
- // checks if a link with the given anchors exists
- public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
- return -1 !== LinkManager.Instance.allLinks().findIndex(linkDoc =>
- (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor2)) ||
- (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor1)));
- }
-
// finds the opposite anchor of a given anchor in a link
//TODO This should probably return undefined if there isn't an opposite anchor
//TODO This should also await the return value of the anchor so we don't filter out promises
@@ -99,14 +137,14 @@ 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
+ // 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) => {
+ public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => {
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 ? "lightbox" : StrCast(sourceDoc.followLinkLocation, followLoc);
docViewProps.addDocTab(doc, where);
setTimeout(() => {
const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
@@ -132,7 +170,7 @@ export class LinkManager {
docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
}
};
- LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
+ LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
}
public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
@@ -159,7 +197,7 @@ export class LinkManager {
const containerDoc = Cast(target.annotationOn, Doc, null) || target;
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, sourceDoc, finished);
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished);
}
} else {
finished?.();