aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CurrentUserUtils.ts63
-rw-r--r--src/client/util/DocumentManager.ts33
-rw-r--r--src/client/util/GroupManager.tsx134
-rw-r--r--src/client/util/GroupMemberView.tsx4
-rw-r--r--src/client/util/LinkManager.ts101
-rw-r--r--src/client/util/SelectionManager.ts8
-rw-r--r--src/client/util/SerializationHelper.ts2
-rw-r--r--src/client/util/SharingManager.tsx91
-rw-r--r--src/client/util/SnappingManager.ts10
9 files changed, 205 insertions, 241 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ec550c15a..7535d7c24 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,6 @@
import { computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
-import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, AclReadonly } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
@@ -8,12 +8,14 @@ import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
+import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
+import { SharingPermissions } from "../../fields/util";
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
import { DocumentType } from "../documents/DocumentTypes";
+import { Networking } from "../Network";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
@@ -30,9 +32,10 @@ import { Scripting } from "./Scripting";
import { SearchUtil } from "./SearchUtil";
import { SelectionManager } from "./SelectionManager";
import { UndoManager } from "./UndoManager";
-import { SharingPermissions } from "../../fields/util";
+import { SnappingManager } from "./SnappingManager";
+export let resolvedPorts: { server: number, socket: number };
const headerViewVersion = "0.1";
export class CurrentUserUtils {
private static curr_id: string;
@@ -229,7 +232,7 @@ export class CurrentUserUtils {
} else {
const curButnTypes = Cast(doc["template-buttons"], Doc, null);
DocListCastAsync(curButnTypes.data).then(async curBtns => {
- await Promise.all(curBtns!);
+ curBtns && await Promise.all(curBtns);
requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
});
}
@@ -278,7 +281,7 @@ export class CurrentUserUtils {
const curNoteTypes = Cast(doc["template-notes"], Doc, null);
const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc];
DocListCastAsync(curNoteTypes.data).then(async curNotes => {
- await Promise.all(curNotes!);
+ curNotes && await Promise.all(curNotes);
requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype));
});
}
@@ -349,7 +352,7 @@ export class CurrentUserUtils {
const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
- await Promise.all(curIcons!);
+ curIcons && await Promise.all(curIcons);
requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
});
}
@@ -538,9 +541,9 @@ export class CurrentUserUtils {
})) as any as Doc;
}
}
- static async setupMenuPanel(doc: Doc, sharingDocumentId: string) {
+ static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (doc.menuStack === undefined) {
- await this.setupSharingSidebar(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing
+ await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing
const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) =>
Docs.Create.FontIconDocument({
icon,
@@ -873,7 +876,16 @@ export class CurrentUserUtils {
}
// Sharing sidebar is where shared documents are contained
- static async setupSharingSidebar(doc: Doc, sharingDocumentId: string) {
+ static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ if (doc.myLinkDatabase === undefined) {
+ let linkDocs = await DocServer.GetRefField(linkDatabaseId);
+ if (!linkDocs) {
+ linkDocs = new Doc(linkDatabaseId, true);
+ (linkDocs as Doc).data = new List<Doc>([]);
+ (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add;
+ }
+ doc.myLinkDatabase = new PrefetchProxy(linkDocs);
+ }
if (doc.mySharedDocs === undefined) {
let sharedDocs = await DocServer.GetRefField(sharingDocumentId + "outer");
if (!sharedDocs) {
@@ -884,7 +896,7 @@ export class CurrentUserUtils {
(sharedDocs as Doc)["acl-Public"] = Doc.GetProto(sharedDocs as Doc)["acl-Public"] = SharingPermissions.Add;
}
if (sharedDocs instanceof Doc) {
- sharedDocs.userColor = sharedDocs.userColor || "#12121233";
+ sharedDocs.userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
}
doc.mySharedDocs = new PrefetchProxy(sharedDocs);
}
@@ -953,7 +965,15 @@ export class CurrentUserUtils {
return doc.clickFuncs as Doc;
}
- static async updateUserDocument(doc: Doc, sharingDocumentId: string) {
+ static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
+ const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
+ reaction(() => DateCast((doc.globalGroupDatabase as Doc).lastModified),
+ async () => {
+ const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
+ const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
+ SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
+ }, { fireImmediately: true });
doc.system = true;
doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
doc.title = Doc.CurrentUserEmail;
@@ -985,10 +1005,8 @@ export class CurrentUserUtils {
this.setupOverlays(doc); // documents in overlay layer
this.setupDockedButtons(doc); // the bottom bar of font icons
await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
- await this.setupMenuPanel(doc, sharingDocumentId);
- doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument();
- doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
- doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
+ await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId);
+ if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered
@@ -1009,9 +1027,13 @@ export class CurrentUserUtils {
}
public static async loadCurrentUser() {
- return rp.get(Utils.prepend("/getCurrentUser")).then(response => {
+ return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
if (response) {
- const result: { id: string, email: string } = JSON.parse(response);
+ const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response);
+ Doc.CurrentUserEmail = result.email;
+ resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts"));
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
+ result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";")));
return result;
} else {
throw new Error("There should be a user! Why does Dash think there isn't one?");
@@ -1019,14 +1041,13 @@ export class CurrentUserUtils {
});
}
- public static async loadUserDocument({ id, email }: { id: string, email: string }) {
+ public static async loadUserDocument(id: string) {
this.curr_id = id;
- Doc.CurrentUserEmail = email;
await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
- const { userDocumentId, sharingDocumentId } = JSON.parse(ids);
+ const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
if (userDocumentId !== "guest") {
return DocServer.GetRefField(userDocumentId).then(async field =>
- this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId));
+ this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId, linkDatabaseId));
} else {
throw new Error("There should be a user id! Why does Dash think there isn't one?");
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 2ca29cb7e..b37181e57 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -10,6 +10,8 @@ import { DocumentView } from '../views/nodes/DocumentView';
import { LinkManager } from './LinkManager';
import { Scripting } from './Scripting';
import { SelectionManager } from './SelectionManager';
+import { LinkDocPreview } from '../views/nodes/LinkDocPreview';
+import { FormattedTextBoxComment } from '../views/nodes/formattedText/FormattedTextBoxComment';
export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
@@ -151,7 +153,7 @@ export class DocumentManager {
};
const docView = getFirstDocView(targetDoc, originatingDoc);
let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
- if (annotatedDoc && !targetDoc?.isPushpin) {
+ if (annotatedDoc && annotatedDoc !== originatingDoc?.context && !targetDoc?.isPushpin) {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
@@ -159,15 +161,25 @@ export class DocumentManager {
}
}
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.Document.hidden = !docView.props.Document.hidden;
+ const hide = !docView.props.Document.hidden;
+ docView.props.focus(docView.props.Document, willZoom, undefined, (notfocused: boolean) => { // bcz: Argh! TODO: Need to add a notFocused argument to the after finish callback function that indicates whether the window had to scroll to show the target
+ if (notfocused || docView.props.Document.hidden) {
+ docView.props.Document.hidden = !docView.props.Document.hidden;
+ }
+ return focusAndFinish();
+ // @ts-ignore bcz: Argh TODO: Need to add a parameter to focus() everywhere for whether focus should center the target's container in the view or not. // here we don't want to focus the container if the source and target are in the same container
+ }, sameContext);
+ //finished?.();
}
else {
docView.select(false);
docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
- docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
- highlight();
+ // @ts-ignore
+ docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish, sameContext);
}
+ highlight();
} else {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
@@ -178,7 +190,7 @@ export class DocumentManager {
highlight();
} else { // otherwise try to get a view of the context of the target
const targetDocContextView = getFirstDocView(targetDocContext);
- targetDocContext._scrollY = NumCast(targetDocContext._scrollTop, 0); // this will force PDFs to activate and load their annotations / allow scrolling
+ targetDocContext._scrollY = targetDocContext._scrollPY = 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);
@@ -193,11 +205,13 @@ export class DocumentManager {
if (retryDocView) { // we found the target in the context
retryDocView.props.focus(targetDoc, willZoom, undefined, focusAndFinish); // focus on the target in the context
highlight();
- }
- if (delay > 2500) {
+ } 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);
- // targetDoc.layout && createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
+ if (targetDoc.layout) {
+ Doc.SetInPlace(targetDoc, "annotationOn", undefined, false);
+ createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
+ }
} else {
setTimeout(() => findView(delay + 250), 250);
}
@@ -213,8 +227,9 @@ export class DocumentManager {
}
public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+ LinkDocPreview.TargetDoc = undefined;
+ FormattedTextBoxComment.linkDoc = undefined;
const linkDocs = link ? [link] : DocListCast(doc.links);
- SelectionManager.DeselectAll();
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
const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index e81c95d83..6458de0ed 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -5,15 +5,15 @@ import * as React from "react";
import Select from 'react-select';
import * as RequestPromise from "request-promise";
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
-import { Cast, StrCast } from "../../fields/Types";
-import { setGroups } from "../../fields/util";
+import { StrCast, Cast } from "../../fields/Types";
import { Utils } from "../../Utils";
-import { DocServer } from "../DocServer";
import { MainViewModal } from "../views/MainViewModal";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
import "./GroupManager.scss";
import { GroupMemberView } from "./GroupMemberView";
import { SharingManager, User } from "./SharingManager";
+import { listSpec } from "../../fields/Schema";
+import { DateField } from "../../fields/DateField";
/**
* Interface for options for the react-select component
@@ -34,58 +34,23 @@ export class GroupManager extends React.Component<{}> {
@observable private createGroupModalOpen: boolean = false;
private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button
- private currentUserGroups: string[] = []; // the list of groups the current user is a member of
@observable private buttonColour: "#979797" | "black" = "#979797";
@observable private groupSort: "ascending" | "descending" | "none" = "none";
- private populating: boolean = false;
-
-
constructor(props: Readonly<{}>) {
super(props);
GroupManager.Instance = this;
}
- /**
- * Populates the list of users and groups.
- */
- componentDidMount() {
- this.populateUsers();
- this.populateGroups();
- }
+ componentDidMount() { this.populateUsers(); }
/**
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- if (!this.populating) {
- this.populating = true;
- runInAction(() => this.users = []);
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
- const raw = JSON.parse(userList) as User[];
- const evaluating = raw.map(async user => {
- const userSharingDocument = await DocServer.GetRefField(user.sharingDocumentId);
- if (userSharingDocument instanceof Doc) {
- const notificationDoc = await Cast(userSharingDocument.data, Doc, null);
- runInAction(() => notificationDoc && this.users.push(user.email));
- }
- });
- return Promise.all(evaluating).then(() => this.populating = false);
- }
- }
-
- /**
- * Populates the list of groups the current user is a member of and sets this list to be used in the GetEffectiveAcl in util.ts
- */
- populateGroups = () => {
- DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
- groups?.forEach(group => {
- const members: string[] = JSON.parse(StrCast(group.members));
- if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
- });
- this.currentUserGroups.push("Public");
- setGroups(this.currentUserGroups);
- });
+ const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const raw = JSON.parse(userList) as User[];
+ raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
}
/**
@@ -103,7 +68,6 @@ export class GroupManager extends React.Component<{}> {
// SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
- this.populateGroups();
}
/**
@@ -122,25 +86,24 @@ export class GroupManager extends React.Component<{}> {
/**
* @returns the database of groups.
*/
- get GroupManagerDoc(): Doc | undefined {
- return Doc.UserDoc().globalGroupDatabase as Doc;
- }
+ @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; }
/**
* @returns a list of all group documents.
*/
- getAllGroups(): Doc[] {
- const groupDoc = this.GroupManagerDoc;
- return groupDoc ? DocListCast(groupDoc.data) : [];
- }
+ @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); }
+
+ /**
+ * @returns the members of the admin group.
+ */
+ @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; }
/**
* @returns a group document based on the group name.
* @param groupName
*/
getGroup(groupName: string): Doc | undefined {
- const groupDoc = this.getAllGroups().find(group => group.groupName === groupName);
- return groupDoc;
+ return this.allGroups.find(group => group.title === groupName);
}
/**
@@ -148,15 +111,9 @@ export class GroupManager extends React.Component<{}> {
*/
getGroupMembers(group: string | Doc): string[] {
if (group instanceof Doc) return JSON.parse(StrCast(group.members)) as string[];
- else return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
+ return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
}
- /**
- * @returns the members of the admin group.
- */
- get adminGroupMembers(): string[] {
- return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : "";
- }
/**
* @returns a boolean indicating whether the current user has access to edit group documents.
@@ -174,14 +131,11 @@ export class GroupManager extends React.Component<{}> {
* @param memberEmails
*/
createGroupDoc(groupName: string, memberEmails: string[] = []) {
- const groupDoc = new Doc;
- groupDoc.groupName = groupName.toLowerCase() === "admin" ? "Admin" : groupName;
+ const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName;
+ const groupDoc = new Doc("GROUP:" + name, true);
+ groupDoc.title = name;
groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
groupDoc.members = JSON.stringify(memberEmails);
- if (memberEmails.includes(Doc.CurrentUserEmail)) {
- this.currentUserGroups.push(groupName);
- setGroups(this.currentUserGroups);
- }
this.addGroup(groupDoc);
}
@@ -192,6 +146,7 @@ export class GroupManager extends React.Component<{}> {
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc);
+ this.GroupManagerDoc.lastModified = new DateField;
return true;
}
return false;
@@ -201,19 +156,20 @@ export class GroupManager extends React.Component<{}> {
* Deletes a group from the database of group documents and @returns whether the group was deleted or not.
* @param group
*/
+ @action
deleteGroup(group: Doc): boolean {
if (group) {
if (this.GroupManagerDoc && this.hasEditAccess(group)) {
Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
SharingManager.Instance.removeGroup(group);
- const members: string[] = JSON.parse(StrCast(group.members));
+ const members = JSON.parse(StrCast(group.members));
if (members.includes(Doc.CurrentUserEmail)) {
- const index = this.currentUserGroups.findIndex(groupName => groupName === group.groupName);
- index !== -1 && this.currentUserGroups.splice(index, 1);
- setGroups(this.currentUserGroups);
+ const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
+ index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
+ this.GroupManagerDoc.lastModified = new DateField;
if (group === this.currentGroup) {
- runInAction(() => this.currentGroup = undefined);
+ this.currentGroup = undefined;
}
return true;
}
@@ -228,10 +184,11 @@ export class GroupManager extends React.Component<{}> {
*/
addMemberToGroup(groupDoc: Doc, email: string) {
if (this.hasEditAccess(groupDoc)) {
- const memberList: string[] = JSON.parse(StrCast(groupDoc.members));
+ const memberList = JSON.parse(StrCast(groupDoc.members));
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.shareWithAddedMember(groupDoc, email);
+ this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField);
}
}
@@ -242,12 +199,13 @@ export class GroupManager extends React.Component<{}> {
*/
removeMemberFromGroup(groupDoc: Doc, email: string) {
if (this.hasEditAccess(groupDoc)) {
- const memberList: string[] = JSON.parse(StrCast(groupDoc.members));
+ const memberList = JSON.parse(StrCast(groupDoc.members));
const index = memberList.indexOf(email);
if (index !== -1) {
const user = memberList.splice(index, 1)[0];
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.removeMember(groupDoc, email);
+ this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField);
}
}
}
@@ -377,14 +335,13 @@ export class GroupManager extends React.Component<{}> {
private get groupInterface() {
const sortGroups = (d1: Doc, d2: Doc) => {
- const g1 = StrCast(d1.groupName);
- const g2 = StrCast(d2.groupName);
+ const g1 = StrCast(d1.title);
+ const g2 = StrCast(d2.title);
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
};
- let groups = this.getAllGroups();
- groups = this.groupSort === "ascending" ? groups.sort(sortGroups) : this.groupSort === "descending" ? groups.sort(sortGroups).reverse() : groups;
+ const groups = this.groupSort === "ascending" ? this.allGroups.sort(sortGroups) : this.groupSort === "descending" ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
return (
<div className="group-interface">
@@ -398,7 +355,7 @@ export class GroupManager extends React.Component<{}> {
<div className="group-heading">
<p><b>Manage Groups</b></p>
<button onClick={action(() => this.createGroupModalOpen = true)}>
- <FontAwesomeIcon icon={"plus-hexagon"} size={"sm"} /> Create Group
+ <FontAwesomeIcon icon={"plus"} size={"sm"} /> Create Group
</button>
<div className={"close-button"} onClick={this.close}>
<FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
@@ -417,9 +374,9 @@ export class GroupManager extends React.Component<{}> {
{groups.map(group =>
<div
className="group-row"
- key={StrCast(group.groupName)}
+ key={StrCast(group.title || group.groupName)}
>
- <div className="group-name" >{group.groupName}</div>
+ <div className="group-name" >{group.title || group.groupName}</div>
<div className="group-info" onClick={action(() => this.currentGroup = group)}>
<FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
</div>
@@ -433,16 +390,13 @@ export class GroupManager extends React.Component<{}> {
}
render() {
- return (
- <MainViewModal
- contents={this.groupInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- dialogueBoxStyle={{ zIndex: 1002 }}
- overlayStyle={{ zIndex: 1001 }}
- closeOnExternalClick={this.close}
- />
- );
+ return <MainViewModal
+ contents={this.groupInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ dialogueBoxStyle={{ zIndex: 1002 }}
+ overlayStyle={{ zIndex: 1001 }}
+ closeOnExternalClick={this.close}
+ />;
}
-
} \ No newline at end of file
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 4ead01e9f..927200ed3 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -33,8 +33,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
<input
className="group-title"
style={{ marginLeft: !hasEditAccess ? "-14%" : 0 }}
- value={StrCast(this.props.group.groupName)}
- onChange={e => this.props.group.groupName = e.currentTarget.value}
+ value={StrCast(this.props.group.title || this.props.group.groupName)}
+ onChange={e => this.props.group.title = e.currentTarget.value}
disabled={!hasEditAccess}
>
</input>
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 269de08a1..1ba6cff6d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,8 +1,7 @@
import { Doc, DocListCast, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
-import { CurrentUserUtils } from "./CurrentUserUtils";
+import { SharingManager } from "./SharingManager";
+import { computedFn } from "mobx-utils";
/*
* link doc:
@@ -33,31 +32,23 @@ export class LinkManager {
private constructor() {
}
- // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes'
- // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type
- public get LinkManagerDoc(): Doc | undefined {
- return Doc.UserDoc().globalLinkDatabase as Doc;
- }
public getAllLinks(): Doc[] {
- const ldoc = LinkManager.Instance.LinkManagerDoc;
- return ldoc ? DocListCast(ldoc.data) : [];
+ const lset = new Set<Doc>(DocListCast(Doc.LinkDBDoc().data));
+ SharingManager.Instance.users.forEach(user => {
+ DocListCast(user.linkDatabase?.data).map(doc => {
+ lset.add(doc);
+ });
+ });
+ return Array.from(lset);
}
public addLink(linkDoc: Doc): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- Doc.AddDocToList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
- return true;
- }
- return false;
+ return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
}
public deleteLink(linkDoc: Doc): boolean {
- if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
- Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
- return true;
- }
- return false;
+ return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc);
}
// finds all links that contain the given anchor
@@ -71,13 +62,18 @@ export class LinkManager {
});
return related;
}
- // finds all links that contain the given anchor
- public getAllRelatedLinks(anchor: Doc): Doc[] {
+
+ relatedLinker = computedFn(function realtedLinker(this: any, anchor: Doc) {
const related = LinkManager.Instance.getAllDirectLinks(anchor);
DocListCast(anchor[Doc.LayoutFieldKey(anchor) + "-annotations"]).map(anno => {
related.push(...LinkManager.Instance.getAllRelatedLinks(anno));
});
return related;
+ }.bind(this), true);
+
+ // finds all links that contain the given anchor
+ public getAllRelatedLinks(anchor: Doc): Doc[] {
+ return this.relatedLinker(anchor);
}
public deleteAllLinksOnAnchor(anchor: Doc) {
@@ -85,49 +81,6 @@ export class LinkManager {
related.forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
}
- public addGroupType(groupType: string): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>([]);
- const groupTypes = LinkManager.Instance.getAllGroupTypes();
- groupTypes.push(groupType);
- LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
- return true;
- }
- return false;
- }
-
- // removes all group docs from all links with the given group type
- public deleteGroupType(groupType: string): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- if (LinkManager.Instance.LinkManagerDoc[groupType]) {
- const groupTypes = LinkManager.Instance.getAllGroupTypes();
- const index = groupTypes.findIndex(type => type.toUpperCase() === groupType.toUpperCase());
- if (index > -1) groupTypes.splice(index, 1);
- LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
- LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
- LinkManager.Instance.getAllLinks().forEach(async linkDoc => {
- const anchor1 = await Cast(linkDoc.anchor1, Doc);
- const anchor2 = await Cast(linkDoc.anchor2, Doc);
- anchor1 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor1, groupType);
- anchor2 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor2, groupType);
- });
- }
- return true;
- } else return false;
- }
-
- public getAllGroupTypes(): string[] {
- if (LinkManager.Instance.LinkManagerDoc) {
- if (LinkManager.Instance.LinkManagerDoc.allGroupTypes) {
- return Cast(LinkManager.Instance.LinkManagerDoc.allGroupTypes, listSpec("string"), []);
- } else {
- LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>([]);
- return [];
- }
- }
- return [];
- }
-
// gets the groups associates with an anchor in a link
public getAnchorGroups(linkDoc: Doc, anchor?: Doc): Array<Doc> {
if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
@@ -164,22 +117,6 @@ export class LinkManager {
return anchorGroups;
}
- // gets a list of strings representing the keys of the metadata associated with the given group type
- public getMetadataKeysInGroup(groupType: string): string[] {
- if (LinkManager.Instance.LinkManagerDoc) {
- return LinkManager.Instance.LinkManagerDoc[groupType] ? Cast(LinkManager.Instance.LinkManagerDoc[groupType], listSpec("string"), []) : [];
- }
- return [];
- }
-
- public setMetadataKeysForGroup(groupType: string, keys: string[]): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>(keys);
- return true;
- }
- return false;
- }
-
// returns a list of all metadata docs associated with the given group type
public getAllMetadataDocsInGroup(groupType: string): Array<Doc> {
const md: Doc[] = [];
@@ -208,6 +145,8 @@ export class LinkManager {
const a2 = Cast(linkDoc.anchor2, Doc, null);
if (Doc.AreProtosEqual(anchor, a1)) return a2;
if (Doc.AreProtosEqual(anchor, a2)) return a1;
+ if (Doc.AreProtosEqual(anchor, a1.annotationOn as Doc)) return a2;
+ if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 008ce281c..34e88c7b0 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,7 +2,6 @@ import { observable, action, runInAction, ObservableMap } from "mobx";
import { Doc, Opt } from "../../fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { computedFn } from "mobx-utils";
-import { List } from "../../fields/List";
import { CollectionSchemaView } from "../views/collections/CollectionSchemaView";
import { CollectionViewType } from "../views/collections/CollectionView";
@@ -67,15 +66,16 @@ export namespace SelectionManager {
manager.SelectSchemaDoc(colSchema, document);
}
+ const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
+ return manager.SelectedDocuments.get(doc) ? true : false;
+ });
// computed functions, such as used in IsSelected generate errors if they're called outside of a
// reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature
// to avoid unnecessary mobx invalidations when running inside a reaction.
export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean {
return !doc ? false : outsideReaction ?
manager.SelectedDocuments.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
- computedFn(function isSelected(doc: DocumentView) { // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
- return manager.SelectedDocuments.get(doc) ? true : false;
- })(doc);
+ IsSelectedCache(doc);
}
export function DeselectAll(except?: Doc): void {
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 19b217726..00ac6e521 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -43,7 +43,7 @@ export namespace SerializationHelper {
}
if (!obj.__type) {
- if (ClientUtils.RELEASE) {
+ if (true || ClientUtils.RELEASE) {
console.warn("No property 'type' found in JSON.");
return undefined;
} else {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index c67ec2db5..914253e3c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import Select from "react-select";
import * as RequestPromise from "request-promise";
-import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt, AclSym, AclAddonly, AclEdit, AclReadonly } from "../../fields/Doc";
+import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt, AclSym, AclAddonly, AclEdit, AclReadonly, DocListCastAsync } from "../../fields/Doc";
import { List } from "../../fields/List";
import { Cast, StrCast } from "../../fields/Types";
import { distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx, normalizeEmail } from "../../fields/util";
@@ -26,6 +26,7 @@ import { SearchBox } from "../views/search/SearchBox";
export interface User {
email: string;
sharingDocumentId: string;
+ linkDatabaseId: string;
}
/**
@@ -52,6 +53,7 @@ const storage = "data";
interface ValidatedUser {
user: User; // database minimal info to identify / communicate with a user (email, sharing doc id)
sharingDoc: Doc; // document to share/message another user
+ linkDatabase: Doc;
userColor: string; // stored on the sharinDoc, extracted for convenience?
}
@@ -83,7 +85,6 @@ export class SharingManager extends React.Component<{}> {
// }
public open = (target?: DocumentView, target_doc?: Doc) => {
- runInAction(() => this.users = []);
this.populateUsers();
runInAction(() => {
this.targetDocView = target;
@@ -123,19 +124,30 @@ export class SharingManager extends React.Component<{}> {
populateUsers = async () => {
if (!this.populating) {
this.populating = true;
- runInAction(() => this.users = []);
const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
const raw = JSON.parse(userList) as User[];
+ const sharingDocs: ValidatedUser[] = [];
const evaluating = raw.map(async user => {
const isCandidate = user.email !== Doc.CurrentUserEmail;
if (isCandidate) {
- const userSharingDoc = await DocServer.GetRefField(user.sharingDocumentId);
- if (userSharingDoc instanceof Doc) {
- runInAction(() => this.users.push({ user, sharingDoc: userSharingDoc, userColor: StrCast(userSharingDoc.userColor) }));
+ const sharingDoc = await DocServer.GetRefField(user.sharingDocumentId);
+ const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId);
+ if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
+ await DocListCastAsync(linkDatabase.data);
+ sharingDocs.push({ user, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.color) });
}
}
});
- return Promise.all(evaluating).then(() => this.populating = false);
+ return Promise.all(evaluating).then(() => {
+ runInAction(() => {
+ for (let i = 0; i < sharingDocs.length; i++) {
+ if (!this.users.find(user => user.user.email === sharingDocs[i].user.email)) {
+ this.users.push(sharingDocs[i]);
+ }
+ }
+ });
+ this.populating = false;
+ });
}
}
@@ -144,10 +156,10 @@ export class SharingManager extends React.Component<{}> {
* @param group
* @param permission
*/
- setInternalGroupSharing = (group: Doc | { groupName: string }, permission: string, targetDoc?: Doc) => {
+ setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
const target = targetDoc || this.targetDoc!;
- const key = normalizeEmail(StrCast(group.groupName));
+ const key = normalizeEmail(StrCast(group.title));
const acl = `acl-${key}`;
const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document);
@@ -164,8 +176,8 @@ export class SharingManager extends React.Component<{}> {
group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]);
users.forEach(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(sharingDoc[storage])) === -1 && Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists
+ if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists
});
}
});
@@ -176,9 +188,19 @@ export class SharingManager extends React.Component<{}> {
* @param group
* @param emailId
*/
- shareWithAddedMember = (group: Doc, emailId: string) => {
- const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
- if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.sharingDoc[storage])) === -1 && Doc.AddDocToList(user.sharingDoc, storage, doc));
+ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
+ const user = this.users.find(({ user: { email } }) => email === emailId)!;
+ const self = this;
+ if (group.docsShared) {
+ if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
+ else {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ }));
+ }
+ }
}
/**
@@ -208,9 +230,12 @@ export class SharingManager extends React.Component<{}> {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
if (group.docsShared) {
- DocListCast(group.docsShared).forEach(doc => {
- Doc.IndexOf(doc, DocListCast(user.sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.sharingDoc, storage, doc); // remove the doc only if it is in the list
- });
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
}
}
@@ -221,7 +246,7 @@ export class SharingManager extends React.Component<{}> {
removeGroup = (group: Doc) => {
if (group.docsShared) {
DocListCast(group.docsShared).forEach(doc => {
- const acl = `acl-${StrCast(group.groupName)}`;
+ const acl = `acl-${StrCast(group.title)}`;
distributeAcls(acl, SharingPermissions.None, doc);
@@ -247,8 +272,8 @@ export class SharingManager extends React.Component<{}> {
doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc);
distributeAcls(acl, permission as SharingPermissions, doc);
- if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(sharingDoc[storage])) === -1 && Doc.AddDocToList(sharingDoc, storage, doc);
- else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
+ if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc);
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
});
}
@@ -399,8 +424,8 @@ export class SharingManager extends React.Component<{}> {
* Sorting algorithm to sort groups.
*/
sortGroups = (group1: Doc, group2: Doc) => {
- const g1 = StrCast(group1.groupName);
- const g2 = StrCast(group2.groupName);
+ const g1 = StrCast(group1.title);
+ const g2 = StrCast(group2.title);
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
}
@@ -409,9 +434,9 @@ export class SharingManager extends React.Component<{}> {
*/
@computed get sharingInterface() {
TraceMobx();
- const groupList = GroupManager.Instance?.getAllGroups() || [];
+ const groupList = GroupManager.Instance?.allGroups || [];
const sortedUsers = this.users.slice().sort(this.sortUsers).map(({ user: { email } }) => ({ label: email, value: indType + email }));
- const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
+ const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
// the next block handles the users shown (individuals/groups/both)
const options: GroupedOptions[] = [];
@@ -448,7 +473,7 @@ export class SharingManager extends React.Component<{}> {
const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
// the list of users shared with
- const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, sharingDoc, userColor }) => {
+ const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, linkDatabase, sharingDoc, userColor }) => {
const userKey = `acl-${normalizeEmail(user.email)}`;
const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]);
const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-";
@@ -464,13 +489,13 @@ export class SharingManager extends React.Component<{}> {
<select
className={"permissions-dropdown"}
value={permissions}
- onChange={e => this.setInternalSharing({ user, sharingDoc: sharingDoc, userColor }, e.currentTarget.value)}
+ onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}
>
{this.sharingOptions(uniform)}
</select>
) : (
<div className={"permissions-dropdown"}>
- {permissions}
+ {permissions === SharingPermissions.Add ? "Can Augment" : permissions}
</div>
)}
</div>
@@ -515,19 +540,19 @@ export class SharingManager extends React.Component<{}> {
// the list of groups shared with
- const groupListMap: (Doc | { groupName: string })[] = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(groupName))}`) : true);
- groupListMap.unshift({ groupName: "Public" }, { groupName: "Override" });
+ const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true);
+ groupListMap.unshift({ title: "Public" }, { title: "Override" });
const groupListContents = groupListMap.map(group => {
- const groupKey = `acl-${StrCast(group.groupName)}`;
+ const groupKey = `acl-${StrCast(group.title)}`;
const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
- const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.groupName)}`]) : "-multiple-";
+ const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : "-multiple-";
return !permissions ? (null) : (
<div
key={groupKey}
className={"container"}
>
- <div className={"padding"}>{group.groupName}</div>
+ <div className={"padding"}>{group.title}</div>
{group instanceof Doc ?
(<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
<FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
@@ -540,7 +565,7 @@ export class SharingManager extends React.Component<{}> {
value={permissions}
onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
>
- {this.sharingOptions(uniform, group.groupName === "Override")}
+ {this.sharingOptions(uniform, group.title === "Override")}
</select>
) : (
<div className={"permissions-dropdown"}>
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index fc07e8ab4..069f81d38 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,4 +1,5 @@
import { observable, action, runInAction } from "mobx";
+import { computedFn } from "mobx-utils";
export namespace SnappingManager {
@@ -14,6 +15,9 @@ export namespace SnappingManager {
this.horizSnapLines = horizLines;
this.vertSnapLines = vertLines;
}
+
+ @observable cachedGroups: string[] = [];
+ @action setCachedGroups(groups: string[]) { this.cachedGroups = groups; }
}
const manager = new Manager();
@@ -25,5 +29,11 @@ export namespace SnappingManager {
export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
export function GetIsDragging() { return manager.IsDragging; }
+
+ /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
+ // need to investigate further what caused the mobx update problems and move to a better location.
+ const getCachedGroupByNameCache = computedFn(function (name: string) { return manager.cachedGroups.includes(name); }, true);
+ export function GetCachedGroupByName(name: string) { return getCachedGroupByNameCache(name); }
+ export function SetCachedGroups(groups: string[]) { manager.setCachedGroups(groups); }
}