aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/util/CurrentUserUtils.ts7
-rw-r--r--src/client/util/GroupManager.tsx71
-rw-r--r--src/client/util/GroupMemberView.tsx4
-rw-r--r--src/client/util/SharingManager.tsx24
-rw-r--r--src/fields/util.ts39
6 files changed, 67 insertions, 80 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7ee8267f8..7d78bd76a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -244,6 +244,7 @@ export namespace Docs {
view: LayoutSource,
dataField: string
},
+ data?: any,
options?: Partial<DocumentOptions>
};
type TemplateMap = Map<DocumentType, PrototypeTemplate>;
@@ -464,6 +465,7 @@ export namespace Docs {
const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
options.layout = layout.view?.LayoutString(layout.dataField);
const doc = Doc.assign(new Doc(prototypeId, true), { system: true, layoutKey: "layout", ...options });
+ doc.data = template.data;
doc.layout_keyValue = KeyValueBox.LayoutString("");
return doc;
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d011d7b09..b4e24f70e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -30,7 +30,7 @@ import { Scripting } from "./Scripting";
import { SearchUtil } from "./SearchUtil";
import { SelectionManager } from "./SelectionManager";
import { UndoManager } from "./UndoManager";
-import { SharingPermissions } from "../../fields/util";
+import { SharingPermissions, UserGroups } from "../../fields/util";
import { Networking } from "../Network";
@@ -988,9 +988,10 @@ export class CurrentUserUtils {
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.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
+ if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
if (!doc.myLinkDatabase) doc.myLinkDatabase = new List([]);
+ UserGroups.setCurrentUserGroups((doc.globalGroupDatabase as Doc).data as List<Doc>);
setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 48e3ca737..a0db46fdb 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,19 +1,18 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
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 { Doc, DocListCast, Opt } from "../../fields/Doc";
import { Cast, StrCast } from "../../fields/Types";
-import { setGroups } from "../../fields/util";
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";
/**
* Interface for options for the react-select component
@@ -34,12 +33,8 @@ 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);
@@ -51,32 +46,15 @@ export class GroupManager extends React.Component<{}> {
*/
componentDidMount() {
this.populateUsers();
- this.populateGroups();
}
/**
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- if (!this.populating) {
- 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)));
- }
- }
-
- /**
- * 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)));
}
/**
@@ -94,7 +72,6 @@ export class GroupManager extends React.Component<{}> {
// SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
- this.populateGroups();
}
/**
@@ -113,14 +90,14 @@ export class GroupManager extends React.Component<{}> {
/**
* @returns the database of groups.
*/
- get GroupManagerDoc(): Doc | undefined {
+ @computed get GroupManagerDoc(): Doc | undefined {
return Doc.UserDoc().globalGroupDatabase as Doc;
}
/**
* @returns a list of all group documents.
*/
- getAllGroups(): Doc[] {
+ @computed get allGroups(): Doc[] {
const groupDoc = this.GroupManagerDoc;
return groupDoc ? DocListCast(groupDoc.data) : [];
}
@@ -130,7 +107,7 @@ export class GroupManager extends React.Component<{}> {
* @param groupName
*/
getGroup(groupName: string): Doc | undefined {
- const groupDoc = this.getAllGroups().find(group => group.groupName === groupName);
+ const groupDoc = this.allGroups.find(group => group.title === groupName);
return groupDoc;
}
@@ -164,15 +141,13 @@ export class GroupManager extends React.Component<{}> {
* @param groupName
* @param memberEmails
*/
+ @action
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);
+ 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,19 +167,19 @@ 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);
}
if (group === this.currentGroup) {
- runInAction(() => this.currentGroup = undefined);
+ this.currentGroup = undefined;
}
return true;
}
@@ -368,13 +343,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();
+ let groups = this.allGroups;
groups = this.groupSort === "ascending" ? groups.sort(sortGroups) : this.groupSort === "descending" ? groups.sort(sortGroups).reverse() : groups;
return (
@@ -408,9 +383,9 @@ export class GroupManager extends React.Component<{}> {
{groups.map(group =>
<div
className="group-row"
- key={StrCast(group.groupName)}
+ key={StrCast(group.title)}
>
- <div className="group-name" >{group.groupName}</div>
+ <div className="group-name" >{group.title}</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>
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 4ead01e9f..d9174561c 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)}
+ onChange={e => this.props.group.title = e.currentTarget.value}
disabled={!hasEditAccess}
>
</input>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 1cc4c59f2..a9abeedaf 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -152,10 +152,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);
@@ -233,7 +233,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);
@@ -411,8 +411,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;
}
@@ -421,7 +421,7 @@ 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) }));
@@ -527,19 +527,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(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(groupName))}`) : 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" }} />
@@ -552,7 +552,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/fields/util.ts b/src/fields/util.ts
index 7293db0c2..1e6707a9a 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -4,7 +4,7 @@ import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
-import { action, trace } from "mobx";
+import { action, trace, observable, reaction } from "mobx";
import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate, ToString, ToScriptString } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
import { ComputedField } from "./ScriptField";
@@ -22,6 +22,24 @@ export function TraceMobx() {
tracing && trace();
}
+// the list of groups that the current user is a member of
+export class UserGroups {
+ @observable static Current: string[];
+ @action static setCurrentUserGroups(groupList: List<Doc>) {
+ reaction(() => groupList,
+ groupList => {
+ UserGroups.Current = [
+ "Public",
+ ...(groupList as List<Doc>).
+ filter(group => group instanceof Doc).
+ map(group => group as Doc).
+ filter(group => JSON.parse(StrCast(group.members))?.includes(Doc.CurrentUserEmail)).
+ map(group => StrCast(group.title))
+ ];
+ }, { fireImmediately: true });
+ }
+}
+
export interface GetterResult {
value: FieldResult;
shouldReturn?: boolean;
@@ -130,14 +148,6 @@ export function denormalizeEmail(email: string) {
// playgroundMode = !playgroundMode;
// }
-// the list of groups that the current user is a member of
-let currentUserGroups: string[] = [];
-
-// called from GroupManager once the groups have been fetched from the server
-export function setGroups(groups: string[]) {
- currentUserGroups = groups;
-}
-
/**
* These are the various levels of access a user can have to a document.
*
@@ -164,6 +174,7 @@ export enum SharingPermissions {
*/
export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol {
if (!target) return AclPrivate;
+ if (target[Id] === "groupdbProto" || target[Id]?.startsWith("GROUP:")) return AclAdmin;
// all changes received fromt the server must be processed as Admin
if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
@@ -171,7 +182,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number,
// if the current user is the author of the document / the current user is a member of the admin group
const userChecked = user || Doc.CurrentUserEmail;
if (userChecked === (target.__fields?.author || target.author)) return AclAdmin;
- if (currentUserGroups.includes("Admin")) return AclAdmin;
+ if (UserGroups.Current?.includes("Admin")) return AclAdmin;
if (target[AclSym] && Object.keys(target[AclSym]).length) {
@@ -192,7 +203,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number,
// there are issues with storing fields with . in the name, so they are replaced with _ during creation
// as a result we need to restore them again during this comparison.
const entity = denormalizeEmail(key.substring(4)); // an individual or a group
- if (currentUserGroups.includes(entity) || userChecked === entity) {
+ if (UserGroups.Current?.includes(entity) || userChecked === entity) {
if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
effectiveAcl = value as symbol;
if (effectiveAcl === AclAdmin) return effectiveAcl;
@@ -308,12 +319,10 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
let prop = in_prop;
- if (in_prop === "toString" || in_prop === ToString || in_prop === ToScriptString || in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];
if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
+ if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop];
if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
- if (prop === LayoutSym) {
- return target.__LAYOUT__;
- }
+ if (prop === LayoutSym) return target.__LAYOUT__;
if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
if (!prop.startsWith("_")) {
console.log(prop + " is deprecated - switch to _" + prop);