aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
authorusodhi <61431818+usodhi@users.noreply.github.com>2020-10-15 22:26:16 +0530
committerusodhi <61431818+usodhi@users.noreply.github.com>2020-10-15 22:26:16 +0530
commit3b81903962ac10bbf87013e045c7fdf101fea9ec (patch)
treedcee81b1fb07c18966272c32975da3899e512228 /src/client/util
parentae57452f05cca70a498e826fb3320bd2182ba88b (diff)
parent377d5c987728c2678ab38565328f1f7ad9f3d135 (diff)
merging
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CurrentUserUtils.ts62
-rw-r--r--src/client/util/DocumentManager.ts11
-rw-r--r--src/client/util/GroupManager.tsx139
-rw-r--r--src/client/util/GroupMemberView.tsx4
-rw-r--r--src/client/util/LinkManager.ts16
-rw-r--r--src/client/util/SelectionManager.ts8
-rw-r--r--src/client/util/SerializationHelper.ts2
-rw-r--r--src/client/util/SharingManager.tsx88
-rw-r--r--src/client/util/SnappingManager.ts10
9 files changed, 159 insertions, 181 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1096b8e5f..dcbeba8cd 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,17 @@ 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).author = Doc.CurrentUserEmail;
+ (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) {
@@ -953,7 +966,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 +1006,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.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
- doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
- if (!doc.myLinkDatabase) doc.myLinkDatabase = new List([]);
+ 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 +1028,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 +1042,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 178daf5f0..a408e1df6 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -164,13 +164,14 @@ export class DocumentManager {
const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context;
if (originatingDoc?.isPushpin) {
const hide = !docView.props.Document.hidden;
- (!hide || !sameContext) && (docView.props.Document.hidden = !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
- notfocused && hide && (docView.props.Document.hidden = true);
+ 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?.();
+ //finished?.();
}
else {
docView.select(false);
@@ -219,7 +220,7 @@ export class DocumentManager {
}
} 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, undefined, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished));
+ () => 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));
}
}
}
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 3264b75f7..a9059ff3d 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 { 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,65 +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[] = ["Public"]; // 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;
- private groupListener: Opt<Lambda>;
- private observableAllGroups: Doc[] = observable([]);
-
-
constructor(props: Readonly<{}>) {
super(props);
GroupManager.Instance = this;
}
- /**
- * Populates the list of users and groups.
- */
- componentDidMount() {
- this.populateUsers();
- this.groupListener = autorun(this.populateGroups);
- }
-
- componentWillUnmount() {
- this.groupListener?.();
- }
+ 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) {
- runInAction(() => 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 = () => {
- this.observableAllGroups.map(g => g.members);
- DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
- groups?.forEach(group => {
- const members: string[] = JSON.parse(StrCast(group.members));
- if (members.includes(Doc.CurrentUserEmail) && this.currentUserGroups.indexOf(StrCast(group.groupName)) === -1) this.currentUserGroups.push(StrCast(group.groupName));
- if (this.observableAllGroups.indexOf(group) === -1) runInAction(() => this.observableAllGroups.push(group));
- });
- setGroups(this.currentUserGroups);
- console.log("populating groups");
- });
+ 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)));
}
/**
@@ -110,7 +68,6 @@ export class GroupManager extends React.Component<{}> {
// SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
- this.populateGroups();
}
/**
@@ -129,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);
}
/**
@@ -155,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.
@@ -181,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);
}
@@ -199,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;
@@ -208,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;
}
@@ -235,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);
}
}
@@ -249,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);
}
}
}
@@ -384,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">
@@ -424,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>
@@ -440,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 56b6cb8a9..1ba6cff6d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,6 +1,4 @@
import { Doc, DocListCast, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
import { SharingManager } from "./SharingManager";
import { computedFn } from "mobx-utils";
@@ -36,17 +34,21 @@ export class LinkManager {
public getAllLinks(): Doc[] {
- const lset = new Set<Doc>(DocListCast(Doc.UserDoc().myLinkDatabase));
- SharingManager.Instance.users.forEach(user => DocListCast(user.sharingDoc.myLinkDatabase).map(lset.add));
+ 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 {
- return Doc.AddDocToList(Doc.UserDoc(), "myLinkDatabase", linkDoc);
+ return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
}
public deleteLink(linkDoc: Doc): boolean {
- return Doc.RemoveDocFromList(Doc.UserDoc(), "myLinkDatabase", linkDoc);
+ return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc);
}
// finds all links that contain the given anchor
@@ -67,7 +69,7 @@ export class LinkManager {
related.push(...LinkManager.Instance.getAllRelatedLinks(anno));
});
return related;
- }.bind(this));
+ }.bind(this), true);
// finds all links that contain the given anchor
public getAllRelatedLinks(anchor: Doc): Doc[] {
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 742811c18..360fad998 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -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?
}
@@ -138,8 +140,10 @@ export class SharingManager extends React.Component<{}> {
const isCandidate = user.email !== Doc.CurrentUserEmail;
if (isCandidate) {
const sharingDoc = await DocServer.GetRefField(user.sharingDocumentId);
- if (sharingDoc instanceof Doc) {
- sharingDocs.push({ user, sharingDoc, userColor: StrCast(sharingDoc.color) });
+ 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) });
}
}
});
@@ -161,10 +165,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);
@@ -181,8 +185,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
});
}
});
@@ -193,22 +197,18 @@ 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)!;
+ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
+ const user = this.users.find(({ user: { email } }) => email === emailId)!;
+ const self = this;
if (group.docsShared) {
- DocListCastAsync(group.docsShared).then(async docs => {
- if (docs) {
- const memberDocs = await DocListCastAsync(user.sharingDoc[storage]);
- memberDocs && docs.forEach(doc => {
- const index = Doc.IndexOf(doc, memberDocs);
- console.log(index);
- console.log(doc);
- index === -1 && (console.log(Doc.AddDocToList(user.sharingDoc, storage, doc)));
- });
- }
-
- });
- // === -1 && Doc.AddDocToList(user.sharingDoc, storage, doc));
+ 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);
+ }));
+ }
}
}
@@ -239,16 +239,12 @@ export class SharingManager extends React.Component<{}> {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
if (group.docsShared) {
- DocListCastAsync(group.docsShared).then(docs => {
- docs?.forEach(doc => {
- DocListCastAsync(user.sharingDoc[storage]).then(sharedDocs => {
- sharedDocs && Doc.IndexOf(doc, sharedDocs) !== -1 && Doc.RemoveDocFromList(user.sharingDoc, storage, doc);
- });
- });
- });
- // 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);
+ })
+ );
}
}
@@ -259,7 +255,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);
@@ -285,8 +281,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));
});
}
@@ -429,8 +425,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;
}
@@ -439,9 +435,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[] = [];
@@ -479,7 +475,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-";
@@ -495,7 +491,7 @@ 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>
@@ -546,19 +542,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" }} />
@@ -571,7 +567,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); }
}