From 467afe6346bf3188461721fa78eed0f9ac214da7 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Wed, 1 Jul 2020 16:55:51 +0530
Subject: ui changes + related functionality changes

---
 src/client/util/GroupManager.tsx | 91 +++++++++++++++++++++-------------------
 1 file changed, 47 insertions(+), 44 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 7c68fc2a0..e352d46a8 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -89,7 +89,8 @@ export default class GroupManager extends React.Component<{}> {
     /**
      * @returns a list of all group documents.
      */
-    private getAllGroups(): Doc[] {
+    // private ?
+    getAllGroups(): Doc[] {
         const groupDoc = this.GroupManagerDoc;
         return groupDoc ? DocListCast(groupDoc.data) : [];
     }
@@ -98,7 +99,8 @@ export default class GroupManager extends React.Component<{}> {
      * @returns a group document based on the group name.
      * @param groupName 
      */
-    private getGroup(groupName: string): Doc | undefined {
+    // private?
+    getGroup(groupName: string): Doc | undefined {
         const groupDoc = this.getAllGroups().find(group => group.groupName === groupName);
         return groupDoc;
     }
@@ -172,8 +174,9 @@ export default class GroupManager extends React.Component<{}> {
     deleteGroup(group: Doc): boolean {
         if (group) {
             if (this.GroupManagerDoc && this.hasEditAccess(group)) {
+                // TODO look at this later
+                // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
                 Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
-                SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
                 if (group === this.currentGroup) {
                     runInAction(() => this.currentGroup = undefined);
                 }
@@ -248,48 +251,48 @@ export default class GroupManager extends React.Component<{}> {
     /**
      * A getter that @returns the interface rendered to view an individual group.
      */
-    private get editingInterface() {
-        const members: string[] = this.currentGroup ? JSON.parse(StrCast(this.currentGroup.members)) : [];
-        const options: UserOptions[] = this.currentGroup ? this.options.filter(option => !(JSON.parse(StrCast(this.currentGroup!.members)) as string[]).includes(option.value)) : [];
-        return (!this.currentGroup ? null :
-            <div className="editing-interface">
-                <div className="editing-header">
-                    <b>{this.currentGroup.groupName}</b>
-                    <div className={"close-button"} onClick={action(() => this.currentGroup = undefined)}>
-                        <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
-                    </div>
+    // private get editingInterface() {
+    //     const members: string[] = this.currentGroup ? JSON.parse(StrCast(this.currentGroup.members)) : [];
+    //     const options: UserOptions[] = this.currentGroup ? this.options.filter(option => !(JSON.parse(StrCast(this.currentGroup!.members)) as string[]).includes(option.value)) : [];
+    //     return (!this.currentGroup ? null :
+    //         <div className="editing-interface">
+    //             <div className="editing-header">
+    //                 <b>{this.currentGroup.groupName}</b>
+    //                 <div className={"close-button"} onClick={action(() => this.currentGroup = undefined)}>
+    //                     <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+    //                 </div>
 
-                    {this.hasEditAccess(this.currentGroup) ?
-                        <div className="group-buttons">
-                            <div className="add-member-dropdown">
-                                <Select
-                                    // isMulti={true}
-                                    isSearchable={true}
-                                    options={options}
-                                    onChange={selectedOption => this.addMemberToGroup(this.currentGroup!, (selectedOption as UserOptions).value)}
-                                    placeholder={"Add members"}
-                                    value={null}
-                                    closeMenuOnSelect={true}
-                                />
-                            </div>
-                            <button onClick={() => this.deleteGroup(this.currentGroup!)}>Delete group</button>
-                        </div> :
-                        null}
-                </div>
-                <div className="editing-contents">
-                    {members.map(member => (
-                        <div className="editing-row">
-                            <div className="user-email">
-                                {member}
-                            </div>
-                            {this.hasEditAccess(this.currentGroup!) ? <button onClick={() => this.removeMemberFromGroup(this.currentGroup!, member)}> Remove </button> : null}
-                        </div>
-                    ))}
-                </div>
-            </div>
-        );
+    //                 {this.hasEditAccess(this.currentGroup) ?
+    //                     <div className="group-buttons">
+    //                         <div className="add-member-dropdown">
+    //                             <Select
+    //                                 // isMulti={true}
+    //                                 isSearchable={true}
+    //                                 options={options}
+    //                                 onChange={selectedOption => this.addMemberToGroup(this.currentGroup!, (selectedOption as UserOptions).value)}
+    //                                 placeholder={"Add members"}
+    //                                 value={null}
+    //                                 closeMenuOnSelect={true}
+    //                             />
+    //                         </div>
+    //                         <button onClick={() => this.deleteGroup(this.currentGroup!)}>Delete group</button>
+    //                     </div> :
+    //                     null}
+    //             </div>
+    //             <div className="editing-contents">
+    //                 {members.map(member => (
+    //                     <div className="editing-row">
+    //                         <div className="user-email">
+    //                             {member}
+    //                         </div>
+    //                         {this.hasEditAccess(this.currentGroup!) ? <button onClick={() => this.removeMemberFromGroup(this.currentGroup!, member)}> Remove </button> : null}
+    //                     </div>
+    //                 ))}
+    //             </div>
+    //         </div>
+    //     );
 
-    }
+    // }
 
     /**
      * A getter that @returns the main interface for the GroupManager.
@@ -307,7 +310,7 @@ export default class GroupManager extends React.Component<{}> {
                 {this.currentGroup ?
                     <GroupMemberView
                         group={this.currentGroup}
-                        onCloseButtonClick={() => this.currentGroup = undefined}
+                        onCloseButtonClick={action(() => this.currentGroup = undefined)}
                     />
                     : null}
                 <div className="group-heading">
-- 
cgit v1.2.3-70-g09d2


From d7f2f6994ce6f2a450dff67b3595a692be9cb977 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 2 Jul 2020 17:23:31 +0530
Subject: restructuring and simplifying group implementation

---
 src/client/util/GroupManager.tsx        |  34 +++++-
 src/client/util/SharingManager.tsx      | 199 +++++++++++++++++---------------
 src/client/views/nodes/DocumentView.tsx |   3 +-
 src/fields/Doc.ts                       |   8 +-
 src/fields/util.ts                      |   2 +-
 5 files changed, 141 insertions(+), 105 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index e352d46a8..c8d3be49b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -3,11 +3,11 @@ import { observable, action, runInAction, computed } from "mobx";
 import { SelectionManager } from "./SelectionManager";
 import MainViewModal from "../views/MainViewModal";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../fields/Doc";
+import { Doc, DocListCast, Opt, DocListCastAsync } from "../../fields/Doc";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
-import SharingManager, { User } from "./SharingManager";
+import { User } from "./SharingManager";
 import { Utils } from "../../Utils";
 import * as RequestPromise from "request-promise";
 import Select from 'react-select';
@@ -33,6 +33,7 @@ export default class GroupManager extends React.Component<{}> {
     @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
     @observable currentGroup: Opt<Doc>; // the currently selected group.
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
+    private currentUserGroups: Doc[] = [];
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -42,6 +43,26 @@ export default class GroupManager extends React.Component<{}> {
     // sets up the list of users
     componentDidMount() {
         this.populateUsers().then(resolved => runInAction(() => this.users = resolved));
+
+        // this.getAllGroups().forEach(group => {
+        //     const members: string[] = JSON.parse(StrCast(group.members));
+        //     if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(group);
+        // });
+        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(group);
+            });
+        })
+            .finally(() => console.log(this.currentUserGroups));
+
+        // (this.GroupManagerDoc?.data as List<Doc>).forEach(group => {
+        //     Promise.resolve(group).then(resolvedGroup => {
+        //         const members: string[] = JSON.parse(StrCast(resolvedGroup.members));
+        //         if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(resolvedGroup);
+        //     });
+        // });
+
     }
 
     /**
@@ -49,8 +70,8 @@ export default class GroupManager extends React.Component<{}> {
      */
     populateUsers = async () => {
         const userList: User[] = JSON.parse(await RequestPromise.get(Utils.prepend("/getUsers")));
-        const currentUserIndex = userList.findIndex(user => user.email === Doc.CurrentUserEmail);
-        currentUserIndex !== -1 && userList.splice(currentUserIndex, 1);
+        // const currentUserIndex = userList.findIndex(user => user.email === Doc.CurrentUserEmail);
+        // currentUserIndex !== -1 && userList.splice(currentUserIndex, 1);
         return userList.map(user => user.email);
     }
 
@@ -61,6 +82,11 @@ export default class GroupManager extends React.Component<{}> {
         return this.users.map(user => ({ label: user, value: user }));
     }
 
+
+    get groupMemberships() {
+        return this.currentUserGroups;
+    }
+
     /**
      * Makes the GroupManager visible.
      */
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 491abe1dc..b4977f8ea 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -85,7 +85,7 @@ export default class SharingManager extends React.Component<{}> {
     @observable private dialogueBoxOpacity = 1;
     @observable private overlayOpacity = 0.4;
     @observable private selectedUsers: UserOptions[] | null = null;
-    @observable private permissions: SharingPermissions = SharingPermissions.None;
+    @observable private permissions: SharingPermissions = SharingPermissions.Edit;
 
     // private get linkVisible() {
     //     return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
@@ -98,9 +98,9 @@ export default class SharingManager extends React.Component<{}> {
             this.targetDoc = target.props.Document;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = true;
-            if (!this.sharingDoc) {
-                this.sharingDoc = new Doc;
-            }
+            // if (!this.sharingDoc) {
+            //     this.sharingDoc = new Doc;
+            // }
         }));
 
         // runInAction(() => this.groups = GroupManager.Instance.getAllGroups());
@@ -116,13 +116,13 @@ export default class SharingManager extends React.Component<{}> {
         }), 500);
     });
 
-    private get sharingDoc() {
-        return this.targetDoc ? Cast(this.targetDoc[SharingKey], Doc) as Doc : undefined;
-    }
+    // private get sharingDoc() {
+    //     return this.targetDoc ? Cast(this.targetDoc[SharingKey], Doc) as Doc : undefined;
+    // }
 
-    private set sharingDoc(value: Doc | undefined) {
-        this.targetDoc && (this.targetDoc[SharingKey] = value);
-    }
+    // private set sharingDoc(value: Doc | undefined) {
+    //     this.targetDoc && (this.targetDoc[SharingKey] = value);
+    // }
 
     constructor(props: {}) {
         super(props);
@@ -153,76 +153,79 @@ export default class SharingManager extends React.Component<{}> {
         const members: string[] = JSON.parse(StrCast(group.members));
         const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
 
-        const sharingDoc = this.sharingDoc!;
-        console.log(sharingDoc)
-        if (permission === SharingPermissions.None) {
-            const metadata = sharingDoc[StrCast(group.groupName)];
-            if (metadata) sharingDoc[StrCast(group.groupName)] = undefined;
-        }
-        else {
-            sharingDoc[StrCast(group.groupName)] = permission;
-        }
+        const target = this.targetDoc!;
+        const ACL = `ACL-${StrCast(group.groupName)}`;
+
+        target[ACL] = permission;
+        // const sharingDoc = this.sharingDoc!;
+        // if (permission === SharingPermissions.None) {
+        //     const metadata = sharingDoc[StrCast(group.groupName)];
+        //     if (metadata) sharingDoc[StrCast(group.groupName)] = undefined;
+        // }
+        // else {
+        //     sharingDoc[StrCast(group.groupName)] = permission;
+        // }
 
-        users.forEach(user => {
-            this.setInternalSharing(user, permission, group);
+        users.forEach(({ notificationDoc }) => {
+            Doc.AddDocToList(notificationDoc, storage, target);
         });
     }
 
-    setInternalSharing = async (recipient: ValidatedUser, state: string, group?: Doc) => {
+    setInternalSharing = (recipient: ValidatedUser, permission: string, group?: Doc) => {
         const { user, notificationDoc } = recipient;
         const target = this.targetDoc!;
-        const manager = this.sharingDoc!;
+        // const manager = this.sharingDoc!;
         const key = user.userDocumentId;
 
-        let metadata = await DocCastAsync(manager[key]);
-        const permissions: { [key: string]: number } = metadata?.permissions ? JSON.parse(StrCast(metadata.permissions)) : {};
-        permissions[StrCast(group ? group.groupName : Doc.CurrentUserEmail)] = parseInt(HierarchyMapping.get(state)!);
-        const max = Math.max(...Object.values(permissions));
-
-        // let max = 0;
-        // const keys: string[] = [];
-        // for (const [key, value] of Object.entries(permissions)) {
-        //     if (value === max && max !== 0) {
-        //         keys.push(key);
-        //     }
-        //     else if (value > max) {
-        //         keys.splice(0, keys.length);
-        //         keys.push(key);
-        //         max = value;
-        //     }
-        // }
+        const ACL = `ACL-${key}`;
 
-        switch (max) {
-            case 0:
-                if (metadata) {
-                    const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
-                    Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
-                    manager[key] = undefined;
-                }
-                break;
-
-            case 1: case 2: case 3:
-                if (!metadata) {
-                    metadata = new Doc;
-                    const sharedAlias = Doc.MakeAlias(target);
-                    Doc.AddDocToList(notificationDoc, storage, sharedAlias);
-                    metadata.sharedAlias = sharedAlias;
-                    manager[key] = metadata;
-                }
-                metadata.permissions = JSON.stringify(permissions);
-                // metadata.usersShared = JSON.stringify(keys);
-                break;
-        }
+        // const permissions: { [key: string]: number } = target[ACL] ? JSON.parse(StrCast(target[ACL])) : {};
+
+        target[ACL] = permission;
+
+        Doc.AddDocToList(notificationDoc, storage, target);
 
-        if (metadata) metadata.maxPermission = HierarchyMapping.get(`${max}`);
+
+        // let metadata = await DocCastAsync(manager[key]);
+        // const permissions: { [key: string]: number } = metadata?.permissions ? JSON.parse(StrCast(metadata.permissions)) : {};
+        // permissions[StrCast(group ? group.groupName : Doc.CurrentUserEmail)] = parseInt(HierarchyMapping.get(permission)!);
+        // const max = Math.max(...Object.values(permissions));
+
+        // switch (max) {
+        //     case 0:
+        //         // if (metadata) {
+        //         //     const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
+        //         //     Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
+        //         //     manager[key] = undefined;
+        //         // }
+        //         Doc.RemoveDocFromList(notificationDoc, storage, target);
+        //         break;
+
+        //     case 1: case 2: case 3:
+
+        //         Doc.AddDocToList(notificationDoc, storage, target);
+
+        //         if (!metadata) {
+        //             metadata = new Doc;
+        //             const sharedAlias = Doc.MakeAlias(target);
+        //             Doc.AddDocToList(notificationDoc, storage, target);
+        //             metadata.sharedAlias = sharedAlias;
+        //             manager[key] = metadata;
+        //         }
+        //         metadata.permissions = JSON.stringify(permissions);
+        //         // metadata.usersShared = JSON.stringify(keys);
+        //         break;
+        // }
+
+        // if (metadata) metadata.maxPermission = HierarchyMapping.get(`${max}`);
     }
 
-    // private setExternalSharing = (state: string) => {
+    // private setExternalSharing = (permission: string) => {
     //     const sharingDoc = this.sharingDoc;
     //     if (!sharingDoc) {
     //         return;
     //     }
-    //     sharingDoc[PublicKey] = state;
+    //     sharingDoc[PublicKey] = permission;
     // }
 
     private get sharingUrl() {
@@ -281,15 +284,21 @@ export default class SharingManager extends React.Component<{}> {
     }
 
     private computePermissions = (userKey: string) => {
-        const sharingDoc = this.sharingDoc;
-        if (!sharingDoc) {
-            return SharingPermissions.None;
-        }
-        const metadata = sharingDoc[userKey] as Doc | string;
-        if (!metadata) {
-            return SharingPermissions.None;
-        }
-        return StrCast(metadata instanceof Doc ? metadata.maxPermission : metadata, SharingPermissions.None);
+        // const sharingDoc = this.sharingDoc;
+        // if (!sharingDoc) {
+        //     return SharingPermissions.None;
+        // }
+        // const metadata = sharingDoc[userKey] as Doc | string;
+
+        if (!this.targetDoc) return SharingPermissions.None;
+
+        const ACL = `ACL-${userKey}`;
+        const permission = StrCast(this.targetDoc[ACL]);
+
+        // if (!metadata) {
+        //     return SharingPermissions.None;
+        // }
+        return StrCast(this.targetDoc[ACL], SharingPermissions.None);
     }
 
     @action
@@ -314,7 +323,6 @@ export default class SharingManager extends React.Component<{}> {
                 this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
             }
         });
-
         this.selectedUsers = null;
     }
 
@@ -377,27 +385,30 @@ export default class SharingManager extends React.Component<{}> {
                 <div className={"hr-substitute"} /> */}
                 <div className="sharing-contents">
                     <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
-                    <div className="share-setup">
-                        <Select
-                            className={"user-search"}
-                            placeholder={"Enter user or group name..."}
-                            isMulti
-                            closeMenuOnSelect={false}
-                            options={options}
-                            onChange={this.handleUsersChange}
-                            value={this.selectedUsers}
-                        />
-                        <select className="permissions-select" onChange={this.handlePermissionsChange}>
-                            {this.sharingOptions}
-                        </select>
-                        <button className="share-button" onClick={this.share}>
-                            Share
-                        </button>
-                    </div>
+                    {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
+                        :
+                        <div className="share-setup">
+                            <Select
+                                className={"user-search"}
+                                placeholder={"Enter user or group name..."}
+                                isMulti
+                                closeMenuOnSelect={false}
+                                options={options}
+                                onChange={this.handleUsersChange}
+                                value={this.selectedUsers}
+                            />
+                            <select className="permissions-select" onChange={this.handlePermissionsChange}>
+                                {this.sharingOptions}
+                            </select>
+                            <button className="share-button" onClick={this.share}>
+                                Share
+                            </button>
+                        </div>
+                    }
                     <div className="main-container">
                         <div className={"individual-container"}>
                             <div className={"users-list"} style={{ display: existOtherUsers ? "block" : "flex", minHeight: existOtherUsers ? undefined : 150 }}>{/*200*/}
-                                {!existOtherUsers ? "There are no other users in your database." :
+                                {
                                     this.users.map(({ user, notificationDoc }) => { // can't use async here
                                         const userKey = user.userDocumentId;
                                         const permissions = this.computePermissions(userKey);
@@ -434,8 +445,8 @@ export default class SharingManager extends React.Component<{}> {
                         </div>
                         <div className={"group-container"}>
                             <div className={"groups-list"} style={{ display: existGroups ? "block" : "flex", minHeight: existOtherUsers ? undefined : 150 }}>{/*200*/}
-                                {!existGroups ? "There are no groups in your database." :
-                                    GroupManager.Instance.getAllGroups().map(group => {
+                                {
+                                    GroupManager.Instance?.getAllGroups().map(group => {
                                         const permissions = this.computePermissions(StrCast(group.groupName));
                                         // const color = ColorMapping.get(permissions);
                                         return permissions === SharingPermissions.None ? null : (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 09eeaee36..21b6d8310 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1169,9 +1169,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        if (!(this.props.Document instanceof Doc)) return (null);
         if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
-        if (this.props.Document.hidden) return (null);
+        if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
         const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e4d11dd4d..7b1db1042 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -96,13 +96,13 @@ export const AclSym = Symbol("Acl");
 export const AclPrivate = Symbol("AclOwnerOnly");
 export const AclReadonly = Symbol("AclReadOnly");
 export const AclAddonly = Symbol("AclAddonly");
-export const AclReadWrite = Symbol("AclReadWrite");
+export const AclEdit = Symbol("AclEdit");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
 const CachedUpdates = Symbol("Cached updates");
 
 
 export function fetchProto(doc: Doc) {
-    if (doc.author !== Doc.CurrentUserEmail) {
+    if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
         const acl = Doc.Get(doc, "ACL", true);
         switch (acl) {
             case "ownerOnly":
@@ -114,8 +114,8 @@ export function fetchProto(doc: Doc) {
             case "addOnly":
                 doc[AclSym] = AclAddonly;
                 break;
-            case "write":
-                doc[AclSym] = AclReadWrite;
+            case "edit":
+                doc[AclSym] = AclEdit;
         }
     }
 
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 2dc21c987..7bb090a93 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -111,7 +111,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
-    if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true;
+    if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
             console.log(prop + " is deprecated - switch to _" + prop);
-- 
cgit v1.2.3-70-g09d2


From 293c62ea3eccbeb2565960abb5ab02fabb5e20a0 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 3 Jul 2020 16:16:36 +0530
Subject: many changes related to adding and removing document from the
 notificationDoc of a user

---
 src/client/DocServer.ts             |   2 +-
 src/client/util/GroupManager.tsx    |  11 +-
 src/client/util/SharingManager.scss |  62 ++++++---
 src/client/util/SharingManager.tsx  | 258 +++++++++++++++++++++++-------------
 src/client/views/MainViewModal.scss |   5 +-
 src/client/views/MainViewModal.tsx  |   1 -
 6 files changed, 218 insertions(+), 121 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 2a7a7c59a..eac53bb02 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -31,7 +31,7 @@ export namespace DocServer {
 
     export enum WriteMode {
         Default = 0, //Anything goes
-        Playground = 1, //Playground (write own/no read)
+        Playground = 1, //Playground (write own/no read other updates)
         LiveReadonly = 2,//Live Readonly (no write/read others)
         LivePlayground = 3,//Live Playground (write own/read others)
     }
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index c8d3be49b..220916ba7 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast, Opt, DocListCastAsync } from "../../fields/Doc";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { User } from "./SharingManager";
+import SharingManager, { User } from "./SharingManager";
 import { Utils } from "../../Utils";
 import * as RequestPromise from "request-promise";
 import Select from 'react-select';
@@ -203,6 +203,7 @@ export default class GroupManager extends React.Component<{}> {
                 // TODO look at this later
                 // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
                 Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
+                SharingManager.Instance.removeGroup(group);
                 if (group === this.currentGroup) {
                     runInAction(() => this.currentGroup = undefined);
                 }
@@ -222,6 +223,7 @@ export default class GroupManager extends React.Component<{}> {
             const memberList: string[] = JSON.parse(StrCast(groupDoc.members));
             !memberList.includes(email) && memberList.push(email);
             groupDoc.members = JSON.stringify(memberList);
+            SharingManager.Instance.shareWithAddedMember(groupDoc, email);
         }
     }
 
@@ -234,8 +236,11 @@ export default class GroupManager extends React.Component<{}> {
         if (this.hasEditAccess(groupDoc)) {
             const memberList: string[] = JSON.parse(StrCast(groupDoc.members));
             const index = memberList.indexOf(email);
-            index !== -1 && memberList.splice(index, 1);
-            groupDoc.members = JSON.stringify(memberList);
+            if (index !== -1) {
+                const user = memberList.splice(index, 1)[0];
+                groupDoc.members = JSON.stringify(memberList);
+                SharingManager.Instance.removeMember(groupDoc, email);
+            }
         }
     }
 
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 209c41651..2708876a3 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -4,6 +4,7 @@
     // display: flex;
     // flex-direction: column;
     width: 600px;
+    height: 360px;
 
     .dialogue-box {
         width: 450;
@@ -14,10 +15,23 @@
         transform: translate(-20px, -20px);
     }
 
+    select {
+        text-align: justify;
+        text-align-last: end
+    }
+
     .sharing-contents {
         display: flex;
         flex-direction: column;
 
+        .close-button {
+            position: absolute;
+            right: 1em;
+            top: 1em;
+            cursor: pointer;
+            z-index: 999;
+        }
+
         .share-setup {
             display: flex;
             margin-bottom: 20px;
@@ -33,11 +47,14 @@
                 margin-left: -100;
                 border: none;
                 outline: none;
+                text-align: justify; // for Edge
+                text-align-last: end;
             }
 
             .share-button {
-                height: 100%;
-                margin-left: 3%;
+                height: 105%;
+                margin-left: 2%;
+                background-color: #979797;
             }
         }
 
@@ -57,8 +74,8 @@
                 .groups-list,
                 .users-list {
                     font-style: italic;
-                    background: white;
-                    border: 1px solid black;
+                    background: gainsboro;
+                    // border: 1px solid black;
                     padding-left: 10px;
                     padding-right: 10px;
                     overflow-y: scroll;
@@ -71,8 +88,14 @@
                     justify-content: center;
                     // color: red;
                     color: black;
-                    height: 150px;
+                    height: 255px;
                     margin: 0 2;
+
+
+                    .none {
+                        font-style: italic;
+
+                    }
                 }
             }
         }
@@ -146,7 +169,7 @@
         font-size: 14;
         font-weight: normal;
         padding: 0;
-        align-items: baseline;
+        align-items: center;
 
         &:hover .padding {
             white-space: unset;
@@ -164,6 +187,7 @@
         .permissions-dropdown {
             border: none;
             height: 25;
+            background: gainsboro;
         }
 
         .edit-actions {
@@ -210,17 +234,17 @@
         }
     }
 
-    .close-button {
-        border-radius: 5px;
-        margin-top: 20px;
-        padding: 10px 0;
-        background: aliceblue;
-        transition: 0.5s ease all;
-        border: 1px solid;
-        border-color: aliceblue;
-    }
-
-    .close-button:hover {
-        border-color: black;
-    }
+    // .close-button {
+    //     border-radius: 5px;
+    //     margin-top: 20px;
+    //     padding: 10px 0;
+    //     background: aliceblue;
+    //     transition: 0.5s ease all;
+    //     border: 1px solid;
+    //     border-color: aliceblue;
+    // }
+
+    // .close-button:hover {
+    //     border-color: black;
+    // }
 }
\ No newline at end of file
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index b4977f8ea..372b6172d 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,7 +1,7 @@
 import { observable, runInAction, action } from "mobx";
 import * as React from "react";
 import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocCastAsync } from "../../fields/Doc";
+import { Doc, Opt, DocCastAsync, DocListCast } from "../../fields/Doc";
 import { DocServer } from "../DocServer";
 import { Cast, StrCast } from "../../fields/Types";
 import * as RequestPromise from "request-promise";
@@ -19,6 +19,8 @@ import { DictationOverlay } from "../views/DictationOverlay";
 import GroupManager, { UserOptions } from "./GroupManager";
 import GroupMemberView from "./GroupMemberView";
 import Select from "react-select";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { List } from "../../fields/List";
 
 library.add(fa.faCopy);
 
@@ -86,6 +88,8 @@ export default class SharingManager extends React.Component<{}> {
     @observable private overlayOpacity = 0.4;
     @observable private selectedUsers: UserOptions[] | null = null;
     @observable private permissions: SharingPermissions = SharingPermissions.Edit;
+    @observable private sharedUsers: ValidatedUser[] = [];
+    @observable private sharedGroups: Doc[] = [];
 
     // private get linkVisible() {
     //     return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
@@ -166,16 +170,52 @@ export default class SharingManager extends React.Component<{}> {
         //     sharingDoc[StrCast(group.groupName)] = permission;
         // }
 
+        group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
+
         users.forEach(({ notificationDoc }) => {
-            Doc.AddDocToList(notificationDoc, storage, target);
+
+            if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+            else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+
         });
     }
 
-    setInternalSharing = (recipient: ValidatedUser, permission: string, group?: Doc) => {
+    shareWithAddedMember = (group: Doc, email: string) => {
+        const user: ValidatedUser = this.users.find(user => user.user.email === email)!;
+
+        if (group.docsShared) {
+            DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
+        }
+    }
+
+    removeMember = (group: Doc, email: string) => {
+        const user: ValidatedUser = this.users.find(user => user.user.email === email)!;
+
+        if (group.docsShared) {
+            DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+        }
+    }
+
+    removeGroup = (group: Doc) => {
+        if (group.docsShared) {
+            DocListCast(group.docsShared).forEach(doc => {
+                const ACL = `ACL-${StrCast(group.groupName)}`;
+                doc[ACL] = "Not Shared";
+
+                const members: string[] = JSON.parse(StrCast(group.members));
+                const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
+
+                users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+            })
+        }
+    }
+
+    setInternalSharing = (recipient: ValidatedUser, permission: string) => {
         const { user, notificationDoc } = recipient;
         const target = this.targetDoc!;
         // const manager = this.sharingDoc!;
-        const key = user.userDocumentId;
+        const key = user.email.replace('.', '_');
+        // const key = user.userDocumentId;
 
         const ACL = `ACL-${key}`;
 
@@ -183,43 +223,47 @@ export default class SharingManager extends React.Component<{}> {
 
         target[ACL] = permission;
 
-        Doc.AddDocToList(notificationDoc, storage, target);
-
-
-        // let metadata = await DocCastAsync(manager[key]);
-        // const permissions: { [key: string]: number } = metadata?.permissions ? JSON.parse(StrCast(metadata.permissions)) : {};
-        // permissions[StrCast(group ? group.groupName : Doc.CurrentUserEmail)] = parseInt(HierarchyMapping.get(permission)!);
-        // const max = Math.max(...Object.values(permissions));
-
-        // switch (max) {
-        //     case 0:
-        //         // if (metadata) {
-        //         //     const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
-        //         //     Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
-        //         //     manager[key] = undefined;
-        //         // }
-        //         Doc.RemoveDocFromList(notificationDoc, storage, target);
-        //         break;
-
-        //     case 1: case 2: case 3:
-
-        //         Doc.AddDocToList(notificationDoc, storage, target);
-
-        //         if (!metadata) {
-        //             metadata = new Doc;
-        //             const sharedAlias = Doc.MakeAlias(target);
-        //             Doc.AddDocToList(notificationDoc, storage, target);
-        //             metadata.sharedAlias = sharedAlias;
-        //             manager[key] = metadata;
-        //         }
-        //         metadata.permissions = JSON.stringify(permissions);
-        //         // metadata.usersShared = JSON.stringify(keys);
-        //         break;
-        // }
 
-        // if (metadata) metadata.maxPermission = HierarchyMapping.get(`${max}`);
+        if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+        else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+
     }
 
+
+    // let metadata = await DocCastAsync(manager[key]);
+    // const permissions: { [key: string]: number } = metadata?.permissions ? JSON.parse(StrCast(metadata.permissions)) : {};
+    // permissions[StrCast(group ? group.groupName : Doc.CurrentUserEmail)] = parseInt(HierarchyMapping.get(permission)!);
+    // const max = Math.max(...Object.values(permissions));
+
+    // switch (max) {
+    //     case 0:
+    //         // if (metadata) {
+    //         //     const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
+    //         //     Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
+    //         //     manager[key] = undefined;
+    //         // }
+    //         Doc.RemoveDocFromList(notificationDoc, storage, target);
+    //         break;
+
+    //     case 1: case 2: case 3:
+
+    //         Doc.AddDocToList(notificationDoc, storage, target);
+
+    //         if (!metadata) {
+    //             metadata = new Doc;
+    //             const sharedAlias = Doc.MakeAlias(target);
+    //             Doc.AddDocToList(notificationDoc, storage, target);
+    //             metadata.sharedAlias = sharedAlias;
+    //             manager[key] = metadata;
+    //         }
+    //         metadata.permissions = JSON.stringify(permissions);
+    //         // metadata.usersShared = JSON.stringify(keys);
+    //         break;
+    // }
+
+    // if (metadata) metadata.maxPermission = HierarchyMapping.get(`${max}`);
+
+
     // private setExternalSharing = (permission: string) => {
     //     const sharingDoc = this.sharingDoc;
     //     if (!sharingDoc) {
@@ -345,6 +389,67 @@ export default class SharingManager extends React.Component<{}> {
             ]
             : [];
 
+        const userListContents: (JSX.Element | null)[] = this.users.map(({ user, notificationDoc }) => { // can't use async here
+            const userKey = user.email.replace('.', '_');
+            // const userKey = user.userDocumentId;
+            const permissions = this.computePermissions(userKey);
+            // const color = ColorMapping.get(permissions);
+
+            // console.log(manager);
+            // const metadata = manager[userKey] as Doc;
+            // const usersShared = StrCast(metadata?.usersShared, "");
+            // console.log(usersShared)
+
+            return permissions === SharingPermissions.None ? null : (
+                <div
+                    key={userKey}
+                    className={"container"}
+                >
+                    <span className={"padding"}>{user.email}</span>
+                    {/* <div className={"shared-by"}>{usersShared}</div> */}
+                    <div className="edit-actions">
+                        <select
+                            className={"permissions-dropdown"}
+                            value={permissions}
+                            // style={{ color, borderColor: color }}
+                            onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
+                        >
+                            {this.sharingOptions}
+                        </select>
+                    </div>
+                </div>
+            );
+        });
+
+
+        const groupListContents = GroupManager.Instance?.getAllGroups().map(group => {
+            const permissions = this.computePermissions(StrCast(group.groupName));
+            // const color = ColorMapping.get(permissions);
+
+            return permissions === SharingPermissions.None ? null : (
+                <div
+                    key={StrCast(group.groupName)}
+                    className={"container"}
+                >
+                    <span className={"padding"}>{group.groupName}</span>
+                    <div className="edit-actions">
+                        <select
+                            className={"permissions-dropdown"}
+                            value={permissions}
+                            // style={{ color, borderColor: color }}
+                            onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
+                        >
+                            {this.sharingOptions}
+                        </select>
+                        <button onClick={action(() => GroupManager.Instance.currentGroup = group)}>Edit</button>
+                    </div>
+                </div>
+            );
+        });
+
+        const displayUserList = userListContents?.every(user => user === null);
+        const displayGroupList = groupListContents?.every(group => group === null);
+
         return (
             <div className={"sharing-interface"}>
                 {GroupManager.Instance?.currentGroup ?
@@ -385,6 +490,9 @@ export default class SharingManager extends React.Component<{}> {
                 <div className={"hr-substitute"} /> */}
                 <div className="sharing-contents">
                     <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
+                    <div className={"close-button"} onClick={this.close}>
+                        <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+                    </div>
                     {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
                         :
                         <div className="share-setup">
@@ -407,69 +515,30 @@ export default class SharingManager extends React.Component<{}> {
                     }
                     <div className="main-container">
                         <div className={"individual-container"}>
-                            <div className={"users-list"} style={{ display: existOtherUsers ? "block" : "flex", minHeight: existOtherUsers ? undefined : 150 }}>{/*200*/}
+                            <div className={"users-list"} style={{ display: displayUserList ? "flex" : "block" }}>{/*200*/}
                                 {
-                                    this.users.map(({ user, notificationDoc }) => { // can't use async here
-                                        const userKey = user.userDocumentId;
-                                        const permissions = this.computePermissions(userKey);
-                                        // const color = ColorMapping.get(permissions);
-
-                                        // console.log(manager);
-                                        // const metadata = manager[userKey] as Doc;
-                                        // const usersShared = StrCast(metadata?.usersShared, "");
-                                        // console.log(usersShared)
-
-
-                                        return permissions === SharingPermissions.None ? null : (
-                                            <div
-                                                key={userKey}
-                                                className={"container"}
-                                            >
-                                                <span className={"padding"}>{user.email}</span>
-                                                {/* <div className={"shared-by"}>{usersShared}</div> */}
-                                                <div className="edit-actions">
-                                                    <select
-                                                        className={"permissions-dropdown"}
-                                                        value={permissions}
-                                                        // style={{ color, borderColor: color }}
-                                                        onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
-                                                    >
-                                                        {this.sharingOptions}
-                                                    </select>
-                                                </div>
-                                            </div>
-                                        );
-                                    })
+                                    displayUserList ?
+                                        <div
+                                            className={"none"}
+                                        >
+                                            There are no users this document has been shared with.
+                                        </div>
+                                        :
+                                        userListContents
                                 }
                             </div>
                         </div>
                         <div className={"group-container"}>
-                            <div className={"groups-list"} style={{ display: existGroups ? "block" : "flex", minHeight: existOtherUsers ? undefined : 150 }}>{/*200*/}
+                            <div className={"groups-list"} style={{ display: displayGroupList ? "flex" : "block" }}>{/*200*/}
                                 {
-                                    GroupManager.Instance?.getAllGroups().map(group => {
-                                        const permissions = this.computePermissions(StrCast(group.groupName));
-                                        // const color = ColorMapping.get(permissions);
-                                        return permissions === SharingPermissions.None ? null : (
-                                            <div
-                                                key={StrCast(group.groupName)}
-                                                className={"container"}
-                                            >
-                                                <span className={"padding"}>{group.groupName}</span>
-                                                <div className="edit-actions">
-                                                    <select
-                                                        className={"permissions-dropdown"}
-                                                        value={permissions}
-                                                        // style={{ color, borderColor: color }}
-                                                        onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
-                                                    >
-                                                        {this.sharingOptions}
-                                                    </select>
-                                                    <button onClick={action(() => GroupManager.Instance.currentGroup = group)}>Edit</button>
-                                                </div>
+                                    displayGroupList ?
+                                        <div
+                                            className={"none"}
+                                        >
+                                            There are no groups this document has been shared with.
                                             </div>
-                                        );
-                                    })
-
+                                        :
+                                        groupListContents
                                 }
 
                             </div>
@@ -477,7 +546,6 @@ export default class SharingManager extends React.Component<{}> {
                     </div>
 
                 </div>
-                <div className={"close-button"} onClick={this.close}>Done</div>
             </div>
         );
     }
diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss
index f5a9ee76c..812fe540b 100644
--- a/src/client/views/MainViewModal.scss
+++ b/src/client/views/MainViewModal.scss
@@ -6,9 +6,10 @@
     align-self: center;
     align-content: center;
     padding: 20px;
-    background: gainsboro;
+    // background: gainsboro;
+    background: white;
     border-radius: 10px;
-    border: 3px solid black;
+    border: 0.5px solid black;
     box-shadow: #00000044 5px 5px 10px;
     transform: translate(-50%, -50%);
     top: 50%;
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index a7bd5882d..c6b3532e8 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -22,7 +22,6 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
                 <div
                     className={"dialogue-box"}
                     style={{
-                        backgroundColor: "gainsboro",
                         borderColor: "black",
                         ...(p.dialogueBoxStyle || {}),
                         opacity: p.isDisplayed ? dialogueOpacity : 0
-- 
cgit v1.2.3-70-g09d2


From 1410392575b411f49601ce039787b6052542b711 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 3 Jul 2020 21:50:56 +0530
Subject: can now exit modal by clicking outside it

---
 src/client/apis/GoogleAuthenticationManager.tsx |  1 +
 src/client/util/GroupManager.tsx                |  1 +
 src/client/util/GroupMemberView.tsx             |  1 +
 src/client/util/SettingsManager.tsx             |  1 +
 src/client/util/SharingManager.tsx              |  1 +
 src/client/views/DictationOverlay.tsx           |  1 +
 src/client/views/MainViewModal.tsx              | 35 +++++++++++++++++++++++++
 src/mobile/AudioUpload.tsx                      |  1 +
 src/mobile/ImageUpload.tsx                      |  1 +
 9 files changed, 43 insertions(+)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index bf4469aeb..5a2bdb13b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -157,6 +157,7 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
                 contents={this.renderPrompt}
                 overlayDisplayedOpacity={0.9}
                 dialogueBoxStyle={this.dialogueBoxStyle}
+                closeOnExternalClick={() => this.isOpen = false}
             />
         );
     }
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 220916ba7..0e710457e 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -387,6 +387,7 @@ export default class GroupManager extends React.Component<{}> {
                 interactive={true}
                 dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
                 overlayDisplayedOpacity={this.overlayOpacity}
+                closeOnExternalClick={this.close}
             />
         );
     }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index c844892b1..4377a1428 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -68,6 +68,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
             isDisplayed={true}
             interactive={true}
             contents={this.editingInterface}
+            closeOnExternalClick={this.props.onCloseButtonClick}
         />;
     }
 
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 9888cce48..fa2b20095 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -134,6 +134,7 @@ export default class SettingsManager extends React.Component<{}> {
                 interactive={true}
                 dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
                 overlayDisplayedOpacity={this.overlayOpacity}
+                closeOnExternalClick={this.close}
             />
         );
     }
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 372b6172d..e74824581 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -559,6 +559,7 @@ export default class SharingManager extends React.Component<{}> {
                 interactive={true}
                 dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
                 overlayDisplayedOpacity={this.overlayOpacity}
+                closeOnExternalClick={this.close}
             />
         );
     }
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 65770c0bb..9ed14509f 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -66,6 +66,7 @@ export class DictationOverlay extends React.Component {
             interactive={false}
             dialogueBoxStyle={dialogueBoxStyle}
             overlayStyle={overlayStyle}
+            closeOnExternalClick={this.initiateDictationFade}
         />);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index c6b3532e8..6041593b8 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,5 +1,8 @@
 import * as React from 'react';
 import "./MainViewModal.scss";
+import { Opt } from '../../fields/Doc';
+import { Lambda, reaction } from 'mobx';
+import { observer } from 'mobx-react';
 
 export interface MainViewOverlayProps {
     isDisplayed: boolean;
@@ -9,10 +12,41 @@ export interface MainViewOverlayProps {
     overlayStyle?: React.CSSProperties;
     dialogueBoxDisplayedOpacity?: number;
     overlayDisplayedOpacity?: number;
+    closeOnExternalClick?: () => void;
 }
 
+@observer
 export default class MainViewModal extends React.Component<MainViewOverlayProps> {
 
+    private ref: React.RefObject<HTMLDivElement> = React.createRef();
+    private displayedListenerDisposer: Opt<Lambda>;
+
+    componentDidMount() {
+
+        document.removeEventListener("click", this.close);
+
+        this.displayedListenerDisposer = reaction(() => this.props.isDisplayed, (isDisplayed) => {
+            if (isDisplayed) document.addEventListener("click", this.close);
+            else document.removeEventListener("click", this.close);
+        });
+    }
+
+    componentWillUnmount() {
+        this.displayedListenerDisposer?.();
+        document.removeEventListener("click", this.close);
+    }
+
+    close = (e: MouseEvent) => {
+
+        const { left, right, top, bottom } = this.ref.current!.getBoundingClientRect();
+
+        if (e.clientX === 0 && e.clientY === 0) return; // why does this happen?
+        if (e.clientX < left || e.clientX > right || e.clientY > bottom || e.clientY < top) {
+            this.props.closeOnExternalClick?.();
+        }
+
+    }
+
     render() {
         const p = this.props;
         const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
@@ -26,6 +60,7 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
                         ...(p.dialogueBoxStyle || {}),
                         opacity: p.isDisplayed ? dialogueOpacity : 0
                     }}
+                    ref={this.ref}
                 >{p.contents}</div>
                 <div
                     className={"overlay"}
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 590bf8f9d..b75102e43 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -143,6 +143,7 @@ export class AudioUpload extends React.Component {
                 interactive={true}
                 dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
                 overlayDisplayedOpacity={this.overlayOpacity}
+                closeOnExternalClick={this.closeUpload}
             />
         );
     }
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 5ea626d52..6a5834f52 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -172,6 +172,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
                 interactive={true}
                 dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
                 overlayDisplayedOpacity={this.overlayOpacity}
+                closeOnExternalClick={this.closeUpload}
             />
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 9cc98267457b0d69d4699b27d881430c169c2c2a Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Sun, 5 Jul 2020 23:35:00 +0530
Subject: simplified exit on outside click + rotating arrow in dropdown menu

---
 src/client/util/GroupManager.tsx    |  7 +++++++
 src/client/util/GroupMemberView.tsx |  7 +++++++
 src/client/views/MainViewModal.tsx  | 33 +--------------------------------
 3 files changed, 15 insertions(+), 32 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 0e710457e..b14dcf55b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -362,6 +362,13 @@ export default class GroupManager extends React.Component<{}> {
                             placeholder={"Select users"}
                             value={this.selectedUsers}
                             closeMenuOnSelect={false}
+                            styles={{
+                                dropdownIndicator: (base, state) => ({
+                                    ...base,
+                                    transition: '0.5s all ease',
+                                    transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+                                })
+                            }}
                         />
                     </div>
                     <div className="group-content">
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 4377a1428..cc279b6b2 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -42,6 +42,13 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                                     placeholder={"Add members"}
                                     value={null}
                                     closeMenuOnSelect={true}
+                                    styles={{
+                                        dropdownIndicator: (base, state) => ({
+                                            ...base,
+                                            transition: '0.5s all ease',
+                                            transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+                                        })
+                                    }}
                                 />
                             </div>
                             <button onClick={() => console.log(GroupManager.Instance.deleteGroup(this.props.group))}>Delete group</button>
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 2bdd159c9..0b73a6ad7 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,7 +1,5 @@
 import * as React from 'react';
 import "./MainViewModal.scss";
-import { Opt } from '../../fields/Doc';
-import { Lambda, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 
 export interface MainViewOverlayProps {
@@ -18,35 +16,6 @@ export interface MainViewOverlayProps {
 @observer
 export default class MainViewModal extends React.Component<MainViewOverlayProps> {
 
-    private ref: React.RefObject<HTMLDivElement> = React.createRef();
-    private displayedListenerDisposer: Opt<Lambda>;
-
-    componentDidMount() {
-
-        document.removeEventListener("pointerdown", this.close);
-
-        this.displayedListenerDisposer = reaction(() => this.props.isDisplayed, (isDisplayed) => {
-            if (isDisplayed) document.addEventListener("pointerdown", this.close);
-            else document.removeEventListener("pointerdown", this.close);
-        });
-    }
-
-    componentWillUnmount() {
-        this.displayedListenerDisposer?.();
-        document.removeEventListener("pointerdown", this.close);
-    }
-
-    close = (e: PointerEvent) => {
-
-        const { left, right, top, bottom } = this.ref.current!.getBoundingClientRect();
-
-        if (e.clientX === 0 && e.clientY === 0) return; // why does this happen?
-        if (e.clientX < left || e.clientX > right || e.clientY > bottom || e.clientY < top) {
-            this.props.closeOnExternalClick?.();
-        }
-
-    }
-
     render() {
         const p = this.props;
         const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
@@ -60,10 +29,10 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
                         ...(p.dialogueBoxStyle || {}),
                         opacity: p.isDisplayed ? dialogueOpacity : 0
                     }}
-                    ref={this.ref}
                 >{p.contents}</div>
                 <div
                     className={"overlay"}
+                    onClick={this.props?.closeOnExternalClick}
                     style={{
                         backgroundColor: "gainsboro",
                         ...(p.overlayStyle || {}),
-- 
cgit v1.2.3-70-g09d2


From f4830de4f8c4794ec98e54be9ba8730e46155c35 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Mon, 6 Jul 2020 18:18:17 +0530
Subject: trying first implementation of storing acls

---
 src/client/DocServer.ts                            |  7 ++-
 src/client/util/GroupManager.tsx                   | 60 +++----------------
 src/client/util/SharingManager.tsx                 | 42 +++++++-------
 src/client/views/DocComponent.tsx                  |  7 ++-
 src/client/views/collections/CollectionView.tsx    |  8 ++-
 src/client/views/nodes/DocumentContentsView.tsx    |  6 +-
 src/client/views/nodes/DocumentView.tsx            |  5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++-
 src/fields/Doc.ts                                  | 67 ++++++++++++++--------
 src/fields/util.ts                                 | 60 +++++++++++++++++--
 10 files changed, 151 insertions(+), 119 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index eac53bb02..860a8fd92 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -39,9 +39,10 @@ export namespace DocServer {
     const docsWithUpdates: { [field: string]: Set<Doc> } = {};
 
     export var PlaygroundFields: string[];
-    export function setPlaygroundFields(livePlayougroundFields: string[]) {
-        DocServer.PlaygroundFields = livePlayougroundFields;
-        livePlayougroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
+    export function setPlaygroundFields(livePlaygroundFields: string[]) {
+        console.log("here");
+        DocServer.PlaygroundFields = livePlaygroundFields;
+        livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
     }
 
     export function setFieldWriteMode(field: string, writeMode: WriteMode) {
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index b14dcf55b..83b206f94 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -33,7 +33,7 @@ export default class GroupManager extends React.Component<{}> {
     @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
     @observable currentGroup: Opt<Doc>; // the currently selected group.
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
-    private currentUserGroups: Doc[] = [];
+    currentUserGroups: string[] = [];
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -51,7 +51,7 @@ export default class GroupManager extends React.Component<{}> {
         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(group);
+                if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
             });
         })
             .finally(() => console.log(this.currentUserGroups));
@@ -82,11 +82,6 @@ export default class GroupManager extends React.Component<{}> {
         return this.users.map(user => ({ label: user, value: user }));
     }
 
-
-    get groupMemberships() {
-        return this.currentUserGroups;
-    }
-
     /**
      * Makes the GroupManager visible.
      */
@@ -151,6 +146,11 @@ export default 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[];
+    }
+
     /**
      * @returns the members of the admin group.
      */
@@ -279,52 +279,6 @@ export default class GroupManager extends React.Component<{}> {
         this.inputRef.current.value = "";
     }
 
-    /**
-     * A getter that @returns the interface rendered to view an individual group.
-     */
-    // private get editingInterface() {
-    //     const members: string[] = this.currentGroup ? JSON.parse(StrCast(this.currentGroup.members)) : [];
-    //     const options: UserOptions[] = this.currentGroup ? this.options.filter(option => !(JSON.parse(StrCast(this.currentGroup!.members)) as string[]).includes(option.value)) : [];
-    //     return (!this.currentGroup ? null :
-    //         <div className="editing-interface">
-    //             <div className="editing-header">
-    //                 <b>{this.currentGroup.groupName}</b>
-    //                 <div className={"close-button"} onClick={action(() => this.currentGroup = undefined)}>
-    //                     <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
-    //                 </div>
-
-    //                 {this.hasEditAccess(this.currentGroup) ?
-    //                     <div className="group-buttons">
-    //                         <div className="add-member-dropdown">
-    //                             <Select
-    //                                 // isMulti={true}
-    //                                 isSearchable={true}
-    //                                 options={options}
-    //                                 onChange={selectedOption => this.addMemberToGroup(this.currentGroup!, (selectedOption as UserOptions).value)}
-    //                                 placeholder={"Add members"}
-    //                                 value={null}
-    //                                 closeMenuOnSelect={true}
-    //                             />
-    //                         </div>
-    //                         <button onClick={() => this.deleteGroup(this.currentGroup!)}>Delete group</button>
-    //                     </div> :
-    //                     null}
-    //             </div>
-    //             <div className="editing-contents">
-    //                 {members.map(member => (
-    //                     <div className="editing-row">
-    //                         <div className="user-email">
-    //                             {member}
-    //                         </div>
-    //                         {this.hasEditAccess(this.currentGroup!) ? <button onClick={() => this.removeMemberFromGroup(this.currentGroup!, member)}> Remove </button> : null}
-    //                     </div>
-    //                 ))}
-    //             </div>
-    //         </div>
-    //     );
-
-    // }
-
     /**
      * A getter that @returns the main interface for the GroupManager.
      */
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index e74824581..bec6b973b 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -43,18 +43,18 @@ export enum SharingPermissions {
 //     [SharingPermissions.Edit, "green"]
 // ]);
 
-const HierarchyMapping = new Map<string, string>([
-    [SharingPermissions.None, "0"],
-    [SharingPermissions.View, "1"],
-    [SharingPermissions.Add, "2"],
-    [SharingPermissions.Edit, "3"],
+// export const HierarchyMapping = new Map<string, number>([
+//     [SharingPermissions.None, 0],
+//     [SharingPermissions.View, 1],
+//     [SharingPermissions.Add, 2],
+//     [SharingPermissions.Edit, 3]
 
-    ["0", SharingPermissions.None],
-    ["1", SharingPermissions.View],
-    ["2", SharingPermissions.Add],
-    ["3", SharingPermissions.Edit]
+//     // ["0", SharingPermissions.None],
+//     // ["1", SharingPermissions.View],
+//     // ["2", SharingPermissions.Add],
+//     // ["3", SharingPermissions.Edit]
 
-]);
+// ]);
 
 interface GroupOptions {
     label: string;
@@ -161,14 +161,6 @@ export default class SharingManager extends React.Component<{}> {
         const ACL = `ACL-${StrCast(group.groupName)}`;
 
         target[ACL] = permission;
-        // const sharingDoc = this.sharingDoc!;
-        // if (permission === SharingPermissions.None) {
-        //     const metadata = sharingDoc[StrCast(group.groupName)];
-        //     if (metadata) sharingDoc[StrCast(group.groupName)] = undefined;
-        // }
-        // else {
-        //     sharingDoc[StrCast(group.groupName)] = permission;
-        // }
 
         group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
 
@@ -206,7 +198,7 @@ export default class SharingManager extends React.Component<{}> {
                 const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
 
                 users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
-            })
+            });
         }
     }
 
@@ -224,8 +216,16 @@ export default class SharingManager extends React.Component<{}> {
         target[ACL] = permission;
 
 
-        if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
-        else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+        if (permission !== SharingPermissions.None) {
+            !this.sharedUsers.includes(recipient) && this.sharedUsers.push(recipient);
+
+            Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+        }
+        else {
+            const index = this.sharedUsers.findIndex(user => user === recipient);
+            index !== -1 && this.sharedUsers.splice(index, 1);
+            Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+        }
 
     }
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9b9a28f0f..e8c34d931 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -7,6 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
+import { getEffectiveAcl } from '../../fields/util';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -137,10 +138,12 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const targetDataDoc = this.props.Document[DataSym];
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
+            console.log("here");
+            const effectiveAcl = getEffectiveAcl(this.dataDoc);
             if (added.length) {
-                if (this.dataDoc[AclSym] === AclReadonly) {
+                if (effectiveAcl === AclReadonly) {
                     return false;
-                } else if (this.dataDoc[AclSym] === AclAddonly) {
+                } else if (effectiveAcl === AclAddonly) {
                     added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
                 } else {
                     added.map(doc => doc.context = this.props.Document);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 26abd2529..0dfe9c52a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, getEffectiveAcl } from '../../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -132,10 +132,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
+        console.log("here");
+        const effectiveAcl = getEffectiveAcl(this.dataDoc);
         if (added.length) {
-            if (this.dataDoc[AclSym] === AclReadonly) {
+            if (effectiveAcl === AclReadonly) {
                 return false;
-            } else if (this.dataDoc[AclSym] === AclAddonly) {
+            } else if (effectiveAcl === AclAddonly) {
                 added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
             } else {
                 added.map(doc => {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f1438fd54..d480c76d0 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -36,7 +36,7 @@ import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
 import React = require("react");
 import { RecommendationsBox } from "../RecommendationsBox";
-import { TraceMobx } from "../../../fields/util";
+import { TraceMobx, getEffectiveAcl } from "../../../fields/util";
 import { ScriptField } from "../../../fields/ScriptField";
 import XRegExp = require("xregexp");
 
@@ -184,8 +184,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
         const bindings = this.CreateBindings(onClick, onInput);
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-
-        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || this.layoutDoc[AclSym] === AclPrivate) ? (null) :
+        console.log("here");
+        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
             <ObserverJsxParser
                 key={42}
                 blacklistedAttrs={[]}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 297a9f2a5..e277ddc36 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,7 @@ import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, getEffectiveAcl } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -1188,7 +1188,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
+        console.log("here");
+        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
         if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc63dfbf5..a0dbcd980 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
 import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
@@ -226,7 +226,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
-            if (!this.dataDoc[AclSym]) {
+            // if (!this.dataDoc[AclSym]) { // what?
+            console.log("here");
+            if (getEffectiveAcl(this.dataDoc) === AclEdit) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bbc9d388a..c965dc282 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,8 +17,9 @@ import { RichTextField } from "./RichTextField";
 import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, getEffectiveAcl } from "./util";
 import { LinkManager } from "../client/util/LinkManager";
+import { SharingPermissions } from "../client/util/SharingManager";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -100,23 +101,39 @@ export const AclEdit = Symbol("AclEdit");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
 const CachedUpdates = Symbol("Cached updates");
 
+const AclMap = new Map<string, symbol>([
+    [SharingPermissions.None, AclPrivate],
+    [SharingPermissions.View, AclReadonly],
+    [SharingPermissions.Add, AclAddonly],
+    [SharingPermissions.Edit, AclEdit]
+]);
 
 export function fetchProto(doc: Doc) {
+    console.log("in fetchproto");
     if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
-        const acl = Doc.Get(doc, "ACL", true);
-        switch (acl) {
-            case "ownerOnly":
-                doc[AclSym] = AclPrivate;
-                return undefined;
-            case "readOnly":
-                doc[AclSym] = AclReadonly;
-                break;
-            case "addOnly":
-                doc[AclSym] = AclAddonly;
-                break;
-            case "edit":
-                doc[AclSym] = AclEdit;
-        }
+
+        const permissions: { [key: string]: symbol } = {};
+
+        Object.keys(doc).forEach(key => {
+            if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!;
+        });
+
+        doc[AclSym] = permissions;
+
+        // const acl = Doc.Get(doc, "ACL", true);
+        // switch (acl) {
+        //     case "ownerOnly":
+        //         doc[AclSym] = AclPrivate;
+        //         return undefined;
+        //     case "readOnly":
+        //         doc[AclSym] = AclReadonly;
+        //         break;
+        //     case "addOnly":
+        //         doc[AclSym] = AclAddonly;
+        //         break;
+        //     // case "edit":
+        //     //     doc[AclSym] = AclEdit;
+        // }
     }
 
     if (doc.proto instanceof Promise) {
@@ -134,10 +151,10 @@ export class Doc extends RefField {
             set: setter,
             get: getter,
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
-            has: (target, key) => target[AclSym] !== AclPrivate && key in target.__fields,
+            has: (target, key) => getEffectiveAcl(target) !== AclPrivate && key in target.__fields,
             ownKeys: target => {
                 const obj = {} as any;
-                if (target[AclSym] !== AclPrivate) Object.assign(obj, target.___fields);
+                if (getEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields);
                 runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
                 return Object.keys(obj);
             },
@@ -191,11 +208,11 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [AclSym]: any = undefined;
+    public [AclSym]: any;
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
-    public [ToString]() { return `Doc(${this[AclSym] === AclPrivate ? "-inaccessible-" : this.title})`; }
+    public [ToString]() { return `Doc(${getEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
     public get [DataSym]() {
         const self = this[SelfProxy];
@@ -823,7 +840,8 @@ export namespace Doc {
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return 0;
+        console.log("here");
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
@@ -832,15 +850,15 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
+        console.log("here");
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
 
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
         return doc;
@@ -870,7 +888,8 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return false;
+        console.log("here");
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
         return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 7bb090a93..a3e7a36f8 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate } from "./Doc";
+import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate, AclEdit, AclReadonly, AclAddonly } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -9,6 +9,7 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast } from "./Types";
+import GroupManager from "../client/util/GroupManager";
 
 
 function _readOnlySetter(): never {
@@ -107,11 +108,57 @@ export function OVERRIDE_ACL(val: boolean) {
     _overrideAcl = val;
 }
 
+const HierarchyMapping = new Map<symbol, number>([
+    [AclPrivate, 0],
+    [AclReadonly, 1],
+    [AclAddonly, 2],
+    [AclEdit, 3]
+]);
+
+export function getEffectiveAcl(target: any): symbol {
+
+    console.log("in getEffectiveAcl");
+
+    if (target[AclSym].ACL) return target[AclSym].ACL;
+
+    let effectiveAcl = AclEdit;
+
+    for (const [key, value] of Object.entries(target[AclSym])) {
+        if (key.startsWith("ACL-")) {
+            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
+                if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
+                    effectiveAcl = value as symbol;
+                    if (effectiveAcl === AclEdit) break;
+                }
+            }
+        }
+    }
+
+    return effectiveAcl;
+}
+
+function testPermission(target: any, in_prop: string | symbol | number): boolean {
+
+    console.log("here");
+    // if (target[AclSym].ACL !== AclEdit && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return false;
+    if (target[AclSym].ACL === AclEdit) return true;
+    for (const [key, value] of Object.entries(target[AclSym])) {
+        if (key.startsWith("ACL-")) {
+            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4))) {
+                if (value === AclEdit) return true;
+            }
+        }
+    }
+    return _overrideAcl || DocServer.PlaygroundFields.includes(in_prop.toString());
+}
+
 const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
+    console.log("in setter")
     let prop = in_prop;
-    if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
+    // if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
+    if (!testPermission(target, in_prop)) return true;
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
             console.log(prop + " is deprecated - switch to _" + prop);
@@ -129,9 +176,11 @@ 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 {
+    console.log("in getter")
     let prop = in_prop;
-    if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
-    if (target[AclSym] === AclPrivate && !_overrideAcl) return undefined;
+    const effectiveAcl = getEffectiveAcl(target);
+    if (in_prop === AclSym) return _overrideAcl ? undefined : effectiveAcl;
+    if (effectiveAcl === AclPrivate && !_overrideAcl) return undefined;
     if (prop === LayoutSym) {
         return target.__LAYOUT__;
     }
@@ -168,7 +217,8 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
     }
     if (field === undefined && !ignoreProto && prop !== "proto") {
         const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
-        if (proto instanceof Doc && proto[AclSym] !== AclPrivate) {
+        console.log("here");
+        if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) {
             return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
         }
         return undefined;
-- 
cgit v1.2.3-70-g09d2


From 12285b8c0aff514a2345874508c35b6de1026ef4 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 9 Jul 2020 12:25:46 +0530
Subject: acls now work I think + some cleanup

---
 src/client/DocServer.ts                            |  1 -
 src/client/util/GroupManager.tsx                   | 13 +++-
 src/client/util/GroupMemberView.tsx                |  2 +-
 src/client/util/SharingManager.tsx                 |  2 -
 src/client/views/DocComponent.tsx                  |  1 -
 src/client/views/GlobalKeyHandler.ts               |  2 +
 src/client/views/collections/CollectionView.tsx    | 21 +++---
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentView.tsx            | 22 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 src/fields/Doc.ts                                  | 24 +------
 src/fields/util.ts                                 | 84 +++++++++++-----------
 12 files changed, 84 insertions(+), 90 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 860a8fd92..bac324c77 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -40,7 +40,6 @@ export namespace DocServer {
 
     export var PlaygroundFields: string[];
     export function setPlaygroundFields(livePlaygroundFields: string[]) {
-        console.log("here");
         DocServer.PlaygroundFields = livePlaygroundFields;
         livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
     }
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 83b206f94..23bdd248b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -14,6 +14,7 @@ import Select from 'react-select';
 import "./GroupManager.scss";
 import { StrCast } from "../../fields/Types";
 import GroupMemberView from "./GroupMemberView";
+import { setGroups } from "../../fields/util";
 
 library.add(fa.faWindowClose);
 
@@ -54,7 +55,7 @@ export default class GroupManager extends React.Component<{}> {
                 if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
             });
         })
-            .finally(() => console.log(this.currentUserGroups));
+            .finally(() => setGroups(this.currentUserGroups));
 
         // (this.GroupManagerDoc?.data as List<Doc>).forEach(group => {
         //     Promise.resolve(group).then(resolvedGroup => {
@@ -178,6 +179,10 @@ export default class GroupManager extends React.Component<{}> {
         groupDoc.groupName = groupName;
         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);
     }
 
@@ -204,6 +209,12 @@ export default class GroupManager extends React.Component<{}> {
                 // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
                 Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
                 SharingManager.Instance.removeGroup(group);
+                const members: string[] = 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);
+                }
                 if (group === this.currentGroup) {
                     runInAction(() => this.currentGroup = undefined);
                 }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index cc279b6b2..742caa676 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -51,7 +51,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                                     }}
                                 />
                             </div>
-                            <button onClick={() => console.log(GroupManager.Instance.deleteGroup(this.props.group))}>Delete group</button>
+                            <button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button>
                         </div> :
                         null}
                 </div>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index bec6b973b..d64302456 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -359,8 +359,6 @@ export default class SharingManager extends React.Component<{}> {
     share = () => {
         this.selectedUsers?.forEach(user => {
             if (user.value.includes(indType)) {
-                console.log(user);
-                console.log(this.users.find(u => u.user.email === user.label));
                 this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
             }
             else {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index e8c34d931..781673e59 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -138,7 +138,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const targetDataDoc = this.props.Document[DataSym];
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
-            console.log("here");
             const effectiveAcl = getEffectiveAcl(this.dataDoc);
             if (added.length) {
                 if (effectiveAcl === AclReadonly) {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index a3a023164..45d53a5f5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -22,6 +22,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 import PDFMenu from "./pdf/PDFMenu";
 import { ContextMenu } from "./ContextMenu";
+import GroupManager from "../util/GroupManager";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -107,6 +108,7 @@ export default class KeyManager {
                 GoogleAuthenticationManager.Instance.cancel();
                 HypothesisAuthenticationManager.Instance.cancel();
                 SharingManager.Instance.close();
+                GroupManager.Instance.close();
                 break;
             case "delete":
             case "backspace":
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 31f0c1df3..6a6a475c8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -132,8 +132,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
-        console.log("here");
-        const effectiveAcl = getEffectiveAcl(this.dataDoc);
+        const effectiveAcl = getEffectiveAcl(this.props.Document);
         if (added.length) {
             if (effectiveAcl === AclReadonly) {
                 return false;
@@ -167,13 +166,15 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
     @action.bound
     removeDocument = (doc: any): boolean => {
-        const docs = doc instanceof Doc ? [doc] : doc as Doc[];
-        const targetDataDoc = this.props.Document[DataSym];
-        const value = DocListCast(targetDataDoc[this.props.fieldKey]);
-        const result = value.filter(v => !docs.includes(v));
-        if (result.length !== value.length) {
-            targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
-            return true;
+        if (getEffectiveAcl(this.props.Document) === AclEdit) {
+            const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+            const targetDataDoc = this.props.Document[DataSym];
+            const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+            const result = value.filter(v => !docs.includes(v));
+            if (result.length !== value.length) {
+                targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+                return true;
+            }
         }
         return false;
     }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d480c76d0..e34ceb994 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -184,7 +184,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
         const bindings = this.CreateBindings(onClick, onInput);
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-        console.log("here");
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
             <ObserverJsxParser
                 key={42}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d76633be..b9ae8b444 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -26,7 +26,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { Scripting } from '../../util/Scripting';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager from '../../util/SharingManager';
+import SharingManager, { SharingPermissions } from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -725,7 +725,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     @action
-    setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+    setAcl = (acl: SharingPermissions) => {
         this.dataDoc.ACL = this.props.Document.ACL = acl;
         DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
             if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
@@ -735,7 +735,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @undoBatch
     @action
-    testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+    testAcl = (acl: SharingPermissions) => {
         this.dataDoc.author = this.props.Document.author = "ADMIN";
         this.dataDoc.ACL = this.props.Document.ACL = acl;
         DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
@@ -845,12 +845,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const existingAcls = cm.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
-        aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
+        aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
+        aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
         !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
@@ -1206,9 +1206,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        console.log("here");
-        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
         if (!(this.props.Document instanceof Doc)) return (null);
+        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
+        if (this.props.Document.hidden) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
         const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a0dbcd980..ccf83cbf9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -227,7 +227,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
             // if (!this.dataDoc[AclSym]) { // what?
-            console.log("here");
             if (getEffectiveAcl(this.dataDoc) === AclEdit) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c965dc282..27eabf451 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -109,7 +109,6 @@ const AclMap = new Map<string, symbol>([
 ]);
 
 export function fetchProto(doc: Doc) {
-    console.log("in fetchproto");
     if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
 
         const permissions: { [key: string]: symbol } = {};
@@ -118,22 +117,8 @@ export function fetchProto(doc: Doc) {
             if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!;
         });
 
-        doc[AclSym] = permissions;
 
-        // const acl = Doc.Get(doc, "ACL", true);
-        // switch (acl) {
-        //     case "ownerOnly":
-        //         doc[AclSym] = AclPrivate;
-        //         return undefined;
-        //     case "readOnly":
-        //         doc[AclSym] = AclReadonly;
-        //         break;
-        //     case "addOnly":
-        //         doc[AclSym] = AclAddonly;
-        //         break;
-        //     // case "edit":
-        //     //     doc[AclSym] = AclEdit;
-        // }
+        if (Object.keys(permissions).length) doc[AclSym] = permissions;
     }
 
     if (doc.proto instanceof Promise) {
@@ -208,7 +193,7 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [AclSym]: any;
+    public [AclSym]: { [key: string]: symbol };
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
@@ -232,8 +217,8 @@ export class Doc extends RefField {
             return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
         }
         return undefined;
-    }
 
+    }
 
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
     public static CurrentUserEmail: string = "";
@@ -840,7 +825,6 @@ export namespace Doc {
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        console.log("here");
         if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
@@ -850,7 +834,6 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-        console.log("here");
         if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
@@ -888,7 +871,6 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        console.log("here");
         if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
         return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a3e7a36f8..a1af1d3c5 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate, AclEdit, AclReadonly, AclAddonly } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -9,7 +9,6 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast } from "./Types";
-import GroupManager from "../client/util/GroupManager";
 
 
 function _readOnlySetter(): never {
@@ -108,57 +107,65 @@ export function OVERRIDE_ACL(val: boolean) {
     _overrideAcl = val;
 }
 
-const HierarchyMapping = new Map<symbol, number>([
-    [AclPrivate, 0],
-    [AclReadonly, 1],
-    [AclAddonly, 2],
-    [AclEdit, 3]
-]);
+let currentUserGroups: string[] = [];
+let currentUserEmail: string;// = Doc.CurrentUserEmail;
 
-export function getEffectiveAcl(target: any): symbol {
+export function setGroups(groups: string[]) {
+    currentUserGroups = groups;
+    currentUserEmail = Doc.CurrentUserEmail;
+}
 
-    console.log("in getEffectiveAcl");
 
-    if (target[AclSym].ACL) return target[AclSym].ACL;
+export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
 
-    let effectiveAcl = AclEdit;
+    const HierarchyMapping = new Map<symbol, number>([
+        [AclPrivate, 0],
+        [AclReadonly, 1],
+        [AclAddonly, 2],
+        [AclEdit, 3]
+    ]);
 
-    for (const [key, value] of Object.entries(target[AclSym])) {
-        if (key.startsWith("ACL-")) {
-            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
-                if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
-                    effectiveAcl = value as symbol;
-                    if (effectiveAcl === AclEdit) break;
-                }
-            }
-        }
+    if (!target[AclSym] && target instanceof Doc) {
+        fetchProto(target);
     }
 
-    return effectiveAcl;
-}
+    if (target[AclSym] && Object.keys(target[AclSym]).length) {
+
+        if (target.author === currentUserEmail) return AclEdit;
+
+        if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
-function testPermission(target: any, in_prop: string | symbol | number): boolean {
+        if (target[AclSym].ACL) return target[AclSym].ACL;
 
-    console.log("here");
-    // if (target[AclSym].ACL !== AclEdit && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return false;
-    if (target[AclSym].ACL === AclEdit) return true;
-    for (const [key, value] of Object.entries(target[AclSym])) {
-        if (key.startsWith("ACL-")) {
-            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4))) {
-                if (value === AclEdit) return true;
+        let effectiveAcl = AclPrivate;
+        let aclPresent = false;
+
+        for (const [key, value] of Object.entries(target[AclSym])) {
+            if (key.startsWith("ACL-")) {
+                if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) {
+                    if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
+                        aclPresent = true;
+                        effectiveAcl = value as symbol;
+                        if (effectiveAcl === AclEdit) break;
+                    }
+                }
             }
         }
+        return aclPresent ? effectiveAcl : AclEdit;
+    }
+    else {
+        return AclEdit;
     }
-    return _overrideAcl || DocServer.PlaygroundFields.includes(in_prop.toString());
 }
 
+
 const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
-    console.log("in setter")
     let prop = in_prop;
-    // if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
-    if (!testPermission(target, in_prop)) return true;
+    if (getEffectiveAcl(target, in_prop) !== AclEdit) {
+        return true;
+    }
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
             console.log(prop + " is deprecated - switch to _" + prop);
@@ -176,11 +183,9 @@ 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 {
-    console.log("in getter")
     let prop = in_prop;
-    const effectiveAcl = getEffectiveAcl(target);
-    if (in_prop === AclSym) return _overrideAcl ? undefined : effectiveAcl;
-    if (effectiveAcl === AclPrivate && !_overrideAcl) return undefined;
+    if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
+    if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined;
     if (prop === LayoutSym) {
         return target.__LAYOUT__;
     }
@@ -217,7 +222,6 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
     }
     if (field === undefined && !ignoreProto && prop !== "proto") {
         const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
-        console.log("here");
         if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) {
             return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
         }
-- 
cgit v1.2.3-70-g09d2


From d180732e4e9cc9291fa71715370763d38d4c4340 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Tue, 14 Jul 2020 22:12:01 +0530
Subject: added group creation modal to GroupManager + much ui

---
 src/client/util/GroupManager.scss    | 174 ++++++++++++++++++---------------
 src/client/util/GroupManager.tsx     | 183 +++++++++++++++++++++--------------
 src/client/util/GroupMemberView.scss |  37 +++++--
 src/client/util/GroupMemberView.tsx  |  21 +++-
 src/client/util/SharingManager.scss  |  17 ++--
 src/client/util/SharingManager.tsx   |  63 ++++++------
 src/client/views/MainViewModal.tsx   |   2 +-
 src/fields/util.ts                   |   4 +-
 8 files changed, 300 insertions(+), 201 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 544a79e98..2da1f0d95 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,23 +1,60 @@
-@import "../views/globalCssVariables";
+// @import "../views/globalCssVariables";
 
 .group-interface {
-    background-color: whitesmoke !important;
-    color: grey;
-    width: 450px;
+    // background-color: whitesmoke !important;
+    // color: grey;
+    width: 550px;
     height: 300px;
 
     .dialogue-box {
-        width: 450;
-        height: 300;
+        .group-create {
+            display: flex;
+            flex-direction: column;
+            height: 90%;
+            justify-content: space-between;
+            // flex-basis: 30%;
+            margin-left: 5px;
+
+            input {
+                border-radius: 5px;
+                // border: none;
+                padding: 8px;
+                min-width: 100%;
+                margin: 4px 0 4px 0;
+                border: 1px solid hsl(0, 0%, 80%);
+                outline: none;
+
+                &:focus {
+                    // border: unset;
+                    border: 2.5px solid #2684FF;
+                }
+            }
+
+            p {
+                font-size: 20px;
+                text-align: left;
+                color: black;
+            }
+
+            button {
+                align-self: flex-end;
+            }
+        }
     }
 
+    //     .dialogue-box {
+    //     width: 450;
+    //     height: 300;
+    // }
+
     button {
-        background: $lighter-alt-accent;
+        // background: $lighter-alt-accent;
+        align-self: center;
         outline: none;
         border-radius: 5px;
         border: 0px;
-        color: #fcfbf7;
-        text-transform: uppercase;
+        // color: #fcfbf7;
+        text-transform: none;
         letter-spacing: 2px;
         font-size: 75%;
         padding: 10px;
@@ -36,12 +73,6 @@
         border-radius: 10px;
     }
 
-    button {
-        width: 100%;
-        align-self: center;
-        background: $darker-alt-accent;
-    }
-
     .delete-button {
         background: rgb(227, 86, 86);
     }
@@ -55,82 +86,73 @@
     }
 
     .group-heading {
-        letter-spacing: .5em;
+        display: flex;
+        align-items: center;
+        margin-bottom: 25px;
+
+        p {
+            font-size: 20px;
+            text-align: left;
+            // margin: 0 0 20px 0;
+            margin-right: 15px;
+            color: black;
+            // width: 60%;
+        }
     }
 
 
     .group-body {
-        display: flex;
+        // display: flex;
         justify-content: space-between;
-        max-height: 80%;
-
-        .group-create {
-            display: flex;
-            flex-direction: column;
-            flex-basis: 30%;
-            margin-left: 5px;
-
-            input {
-                border-radius: 5px;
-                border: none;
-                padding: 4px;
-                min-width: 100%;
-                margin: 4px 0 4px 0;
-            }
+        // max-height: 80%;
+        height: 100%;
+        background-color: #e8e8e8;
+        // flex-direction: column;
 
-        }
-
-        .group-content {
-            padding-left: 1em;
-            padding-right: 1em;
-            justify-content: space-around;
-            text-align: left;
+        // padding-left: 1em;
+        padding-right: 1em;
+        justify-content: space-around;
+        text-align: left;
 
-            overflow-y: auto;
-            width: 100%;
-
-            .group-row {
-                display: flex;
-                position: relative;
-                margin-bottom: 5px;
-                min-height: 40px;
-                border: 1px solid;
-                border-radius: 10px;
-                align-items: center;
-
-                .group-name {
-                    position: relative;
-                    max-width: 65%;
-                    left: 10;
-                }
+        overflow-y: auto;
+        width: 100%;
 
-                button {
-                    position: absolute;
-                    width: 30%;
-                    right: 2;
-                    margin-top: 0;
-                }
+        .group-row {
+            display: flex;
+            // position: relative;
+            margin-bottom: 5px;
+            min-height: 30px;
+            // border: 1px solid;
+            // border-radius: 10px;
+            align-items: center;
+
+            .group-name {
+                // position: relative;
+                max-width: 65%;
+                // left: 10;
+                margin: 0 10;
+                color: black;
             }
 
-            ::placeholder {
-                color: $intermediate-color;
+            .group-info {
+                cursor: pointer;
             }
 
-            input {
-                border-radius: 5px;
-                border: none;
-                padding: 4px;
-                min-width: 100%;
-                margin: 2px 0;
+            button {
+                position: absolute;
+                width: 30%;
+                right: 2;
+                margin-top: 0;
             }
+        }
 
+        input {
+            border-radius: 5px;
+            border: none;
+            padding: 4px;
+            min-width: 100%;
+            margin: 2px 0;
         }
-    }
 
-    h1 {
-        color: $dark-color;
-        text-transform: uppercase;
-        letter-spacing: 2px;
-        font-size: 120%;
     }
 }
\ No newline at end of file
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 23bdd248b..f2b502ae0 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -12,11 +12,12 @@ import { Utils } from "../../Utils";
 import * as RequestPromise from "request-promise";
 import Select from 'react-select';
 import "./GroupManager.scss";
-import { StrCast } from "../../fields/Types";
+import { StrCast, Cast } from "../../fields/Types";
 import GroupMemberView from "./GroupMemberView";
 import { setGroups } from "../../fields/util";
+import { DocServer } from "../DocServer";
 
-library.add(fa.faWindowClose);
+library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle);
 
 export interface UserOptions {
     label: string;
@@ -33,47 +34,37 @@ export default class GroupManager extends React.Component<{}> {
     @observable private users: string[] = []; // list of users populated from the database.
     @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
     @observable currentGroup: Opt<Doc>; // the currently selected group.
+    @observable private createGroupModalOpen: boolean = false;
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
-    currentUserGroups: string[] = [];
+    private currentUserGroups: string[] = [];
+    @observable private buttonColour: "#979797" | "black" = "#979797";
 
     constructor(props: Readonly<{}>) {
         super(props);
         GroupManager.Instance = this;
     }
 
-    // sets up the list of users
-    componentDidMount() {
-        this.populateUsers().then(resolved => runInAction(() => this.users = resolved));
-
-        // this.getAllGroups().forEach(group => {
-        //     const members: string[] = JSON.parse(StrCast(group.members));
-        //     if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(group);
-        // });
-        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));
-            });
-        })
-            .finally(() => setGroups(this.currentUserGroups));
-
-        // (this.GroupManagerDoc?.data as List<Doc>).forEach(group => {
-        //     Promise.resolve(group).then(resolvedGroup => {
-        //         const members: string[] = JSON.parse(StrCast(resolvedGroup.members));
-        //         if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(resolvedGroup);
-        //     });
-        // });
-
-    }
-
     /**
-     * Fetches the list of users stored on the database and @returns a list of the emails.
+     * Fetches the list of users stored on the database.
      */
     populateUsers = async () => {
-        const userList: User[] = JSON.parse(await RequestPromise.get(Utils.prepend("/getUsers")));
-        // const currentUserIndex = userList.findIndex(user => user.email === Doc.CurrentUserEmail);
-        // currentUserIndex !== -1 && userList.splice(currentUserIndex, 1);
-        return userList.map(user => user.email);
+        const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+        const raw = JSON.parse(userList) as User[];
+        const evaluating = raw.map(async user => {
+            const isCandidate = user.email !== Doc.CurrentUserEmail;
+            if (isCandidate) {
+                const userDocument = await DocServer.GetRefField(user.userDocumentId);
+                if (userDocument instanceof Doc) {
+                    const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc);
+                    runInAction(() => {
+                        if (notificationDoc instanceof Doc) {
+                            this.users.push(user.email);
+                        }
+                    });
+                }
+            }
+        });
+        return Promise.all(evaluating);
     }
 
     /**
@@ -90,6 +81,15 @@ export default class GroupManager extends React.Component<{}> {
     open = () => {
         SelectionManager.DeselectAll();
         this.isOpen = true;
+        this.populateUsers();
+        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));
+            });
+
+            setGroups(this.currentUserGroups);
+        });
     }
 
     /**
@@ -99,6 +99,8 @@ export default class GroupManager extends React.Component<{}> {
     close = () => {
         this.isOpen = false;
         this.currentGroup = undefined;
+        this.users = [];
+        this.createGroupModalOpen = false;
     }
 
     /**
@@ -277,6 +279,7 @@ export default class GroupManager extends React.Component<{}> {
      */
     @action
     createGroup = () => {
+        // this.createGroupModalOpen = true;
         if (!this.inputRef.current?.value) {
             alert("Please enter a group name");
             return;
@@ -288,6 +291,66 @@ export default class GroupManager extends React.Component<{}> {
         this.createGroupDoc(this.inputRef.current.value, this.selectedUsers?.map(user => user.value));
         this.selectedUsers = null;
         this.inputRef.current.value = "";
+        this.buttonColour = "#979797";
+    }
+
+    private get groupCreationModal() {
+        const contents = (
+            <div className="group-create">
+                <div className="group-heading" style={{ marginBottom: 0 }}>
+                    <p><b>New Group</b></p>
+                    <div className={"close-button"} onClick={action(() => this.createGroupModalOpen = false)}>
+                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+                    </div>
+                </div>
+                <input
+                    ref={this.inputRef}
+                    onKeyDown={this.handleKeyDown}
+                    type="text"
+                    placeholder="Group name"
+                    onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} />
+                <Select
+                    isMulti={true}
+                    isSearchable={true}
+                    options={this.options}
+                    onChange={this.handleChange}
+                    placeholder={"Select users"}
+                    value={this.selectedUsers}
+                    closeMenuOnSelect={false}
+                    styles={{
+                        dropdownIndicator: (base, state) => ({
+                            ...base,
+                            transition: '0.5s all ease',
+                            transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+                        }),
+                        multiValue: (base) => ({
+                            ...base,
+                            maxWidth: "50%",
+
+                            '&:hover': {
+                                maxWidth: "unset"
+                            }
+                        })
+                    }}
+                />
+                <button onClick={this.createGroup}
+                    style={{ background: this.buttonColour }}
+                    disabled={this.buttonColour === "#979797"}
+                >
+                    Create
+                </button>
+            </div>
+        );
+
+        return (
+            <MainViewModal
+                isDisplayed={this.createGroupModalOpen}
+                interactive={true}
+                contents={contents}
+                dialogueBoxStyle={{ width: "70%", height: "70%" }}
+                closeOnExternalClick={action(() => this.createGroupModalOpen = false)}
+            />
+        );
     }
 
     /**
@@ -296,13 +359,7 @@ export default class GroupManager extends React.Component<{}> {
     private get groupInterface() {
         return (
             <div className="group-interface">
-                {/* <MainViewModal
-                    contents={this.editingInterface}
-                    isDisplayed={this.currentGroup ? true : false}
-                    interactive={true}
-                    dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                    overlayDisplayedOpacity={this.overlayOpacity}
-                /> */}
+                {this.groupCreationModal}
                 {this.currentGroup ?
                     <GroupMemberView
                         group={this.currentGroup}
@@ -310,42 +367,26 @@ export default class GroupManager extends React.Component<{}> {
                     />
                     : null}
                 <div className="group-heading">
-                    <h1>Groups</h1>
+                    <p><b>Manage Groups</b></p>
+                    <button onClick={action(() => this.createGroupModalOpen = true)}>
+                        <FontAwesomeIcon icon={fa.faPlus} size={"sm"} /> Create Group
+                    </button>
                     <div className={"close-button"} onClick={this.close}>
-                        <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
                     </div>
                 </div>
                 <div className="group-body">
-                    <div className="group-create">
-                        <button onClick={this.createGroup}>Create group</button>
-                        <input ref={this.inputRef} onKeyDown={this.handleKeyDown} type="text" placeholder="Group name" />
-                        <Select
-                            isMulti={true}
-                            isSearchable={true}
-                            options={this.options}
-                            onChange={this.handleChange}
-                            placeholder={"Select users"}
-                            value={this.selectedUsers}
-                            closeMenuOnSelect={false}
-                            styles={{
-                                dropdownIndicator: (base, state) => ({
-                                    ...base,
-                                    transition: '0.5s all ease',
-                                    transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
-                                })
-                            }}
-                        />
-                    </div>
-                    <div className="group-content">
-                        {this.getAllGroups().map(group =>
-                            <div className="group-row">
-                                <div className="group-name">{group.groupName}</div>
-                                <button onClick={action(() => this.currentGroup = group)}>
-                                    {this.hasEditAccess(group) ? "Edit" : "View"}
-                                </button>
+                    {this.getAllGroups().map(group =>
+                        <div className="group-row">
+                            <div className="group-name" >{group.groupName}</div>
+                            <div className="group-info" onClick={action(() => this.currentGroup = group)}>
+                                <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
                             </div>
-                        )}
-                    </div>
+                            {/* <button onClick={action(() => this.currentGroup = group)}>
+                                {this.hasEditAccess(group) ? "Edit" : "View"}
+                            </button> */}
+                        </div>
+                    )}
                 </div>
             </div>
         );
diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss
index 7833c485f..9bdf832e0 100644
--- a/src/client/util/GroupMemberView.scss
+++ b/src/client/util/GroupMemberView.scss
@@ -1,18 +1,23 @@
-@import "../views/globalCssVariables";
+// @import "../views/globalCssVariables";
 
 .editing-interface {
-    background-color: whitesmoke !important;
-    color: grey;
+    // background-color: whitesmoke !important;
+    // color: grey;
     width: 100%;
     height: 100%;
+    // color: black;
+
+    hr {
+        margin-top: 10;
+    }
 
     button {
-        background: $darker-alt-accent;
+        // background: $darker-alt-accent;
         outline: none;
         border-radius: 5px;
         border: 0px;
         color: #fcfbf7;
-        text-transform: uppercase;
+        // text-transform: uppercase;
         letter-spacing: 2px;
         font-size: 75%;
         padding: 10px;
@@ -32,12 +37,22 @@
     .editing-header {
         margin-bottom: 5;
 
+        .group-title {
+            font-weight: bold;
+            font-size: 15;
+            text-align: center;
+            border: none;
+            outline: none;
+            color: black;
+            margin-top: -5;
+        }
+
         .group-buttons {
             display: flex;
             margin-top: 5;
 
             .add-member-dropdown {
-                width: 100%;
+                width: 65%;
                 margin: 0 5;
             }
         }
@@ -48,10 +63,13 @@
         // max-height: 67%;
         height: 67%;
         width: 100%;
+        color: black;
+        margin-top: -15px;
 
         .editing-row {
             display: flex;
             align-items: center;
+            margin-bottom: 10px;
             // border: 1px solid;
             // border-radius: 10px;
 
@@ -60,6 +78,13 @@
                 min-width: 65%;
                 word-break: break-all;
                 padding: 0 5;
+                text-align: left;
+            }
+
+            .remove-button {
+                position: absolute;
+                right: 30;
+                cursor: pointer;
             }
         }
     }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 742caa676..2462ee4d5 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -11,7 +11,7 @@ import Select from "react-select";
 import { Doc } from "../../fields/Doc";
 import "./GroupMemberView.scss";
 
-library.add(fa.faWindowClose);
+library.add(fa.faTimes, fa.faTrashAlt);
 
 interface GroupMemberViewProps {
     group: Doc;
@@ -21,17 +21,22 @@ interface GroupMemberViewProps {
 @observer
 export default class GroupMemberView extends React.Component<GroupMemberViewProps> {
 
+
     private get editingInterface() {
         const members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
         const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
         return (!this.props.group ? null :
             <div className="editing-interface">
                 <div className="editing-header">
-                    <b>{this.props.group.groupName}</b>
+                    <input
+                        className="group-title"
+                        value={StrCast(this.props.group.groupName)}
+                        onChange={e => this.props.group.groupName = e.currentTarget.value}
+                    >
+                    </input>
                     <div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}>
-                        <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
                     </div>
-
                     {GroupManager.Instance.hasEditAccess(this.props.group) ?
                         <div className="group-buttons">
                             <div className="add-member-dropdown">
@@ -55,13 +60,18 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                         </div> :
                         null}
                 </div>
+                <hr />
                 <div className="editing-contents">
                     {members.map(member => (
                         <div className="editing-row">
                             <div className="user-email">
                                 {member}
                             </div>
-                            {GroupManager.Instance.hasEditAccess(this.props.group) ? <button onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}> Remove </button> : null}
+                            {GroupManager.Instance.hasEditAccess(this.props.group) ?
+                                <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
+                                    <FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} />
+                                </div>
+                                : null}
                         </div>
                     ))}
                 </div>
@@ -75,6 +85,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
             isDisplayed={true}
             interactive={true}
             contents={this.editingInterface}
+            dialogueBoxStyle={{ width: 400, height: 250 }}
             closeOnExternalClick={this.props.onCloseButtonClick}
         />;
     }
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 2708876a3..ce23ce413 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -6,10 +6,10 @@
     width: 600px;
     height: 360px;
 
-    .dialogue-box {
-        width: 450;
-        height: 300;
-    }
+    // .dialogue-box {
+    //     width: 450;
+    //     height: 300;
+    // }
 
     .overlay {
         transform: translate(-20px, -20px);
@@ -60,11 +60,13 @@
 
         .main-container {
             display: flex;
-
+            margin-top: -10px;
 
             .individual-container,
             .group-container {
                 width: 50%;
+                display: flex;
+                flex-direction: column;
 
                 .share-title {
                     margin-top: 20px;
@@ -74,7 +76,7 @@
                 .groups-list,
                 .users-list {
                     font-style: italic;
-                    background: gainsboro;
+                    background: #e8e8e8;
                     // border: 1px solid black;
                     padding-left: 10px;
                     padding-right: 10px;
@@ -88,7 +90,7 @@
                     justify-content: center;
                     // color: red;
                     color: black;
-                    height: 255px;
+                    height: 250px;
                     margin: 0 2;
 
 
@@ -187,7 +189,6 @@
         .permissions-dropdown {
             border: none;
             height: 25;
-            background: gainsboro;
         }
 
         .edit-actions {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index af68edab6..6c7c634eb 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -22,7 +22,7 @@ import Select from "react-select";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { List } from "../../fields/List";
 
-library.add(fa.faCopy);
+library.add(fa.faCopy, fa.faTimes);
 
 export interface User {
     email: string;
@@ -140,7 +140,7 @@ export default class SharingManager extends React.Component<{}> {
 
     setInternalGroupSharing = (group: Doc, permission: string) => {
         const members: string[] = JSON.parse(StrCast(group.members));
-        const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
+        const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
 
         const target = this.targetDoc!;
         const ACL = `ACL-${StrCast(group.groupName)}`;
@@ -160,8 +160,8 @@ export default class SharingManager extends React.Component<{}> {
         });
     }
 
-    shareWithAddedMember = (group: Doc, email: string) => {
-        const user: ValidatedUser = this.users.find(user => user.user.email === email)!;
+    shareWithAddedMember = (group: Doc, emailId: string) => {
+        const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
 
         if (group.docsShared) {
             DocListCastAsync(group.docsShared).then(docsShared => {
@@ -173,8 +173,8 @@ export default class SharingManager extends React.Component<{}> {
         }
     }
 
-    removeMember = (group: Doc, email: string) => {
-        const user: ValidatedUser = this.users.find(user => user.user.email === email)!;
+    removeMember = (group: Doc, emailId: string) => {
+        const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
 
         if (group.docsShared) {
             DocListCastAsync(group.docsShared).then(docsShared => {
@@ -194,9 +194,9 @@ export default class SharingManager extends React.Component<{}> {
                     doc[ACL] = "Not Shared";
 
                     const members: string[] = JSON.parse(StrCast(group.members));
-                    const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
+                    const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
 
-                    users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+                    users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc));
                 });
 
             });
@@ -330,24 +330,6 @@ export default class SharingManager extends React.Component<{}> {
         );
     }
 
-    private computePermissions = (userKey: string) => {
-        // const sharingDoc = this.sharingDoc;
-        // if (!sharingDoc) {
-        //     return SharingPermissions.None;
-        // }
-        // const metadata = sharingDoc[userKey] as Doc | string;
-
-        if (!this.targetDoc) return SharingPermissions.None;
-
-        const ACL = `ACL-${userKey}`;
-        const permission = StrCast(this.targetDoc[ACL]);
-
-        // if (!metadata) {
-        //     return SharingPermissions.None;
-        // }
-        return StrCast(this.targetDoc[ACL], SharingPermissions.None);
-    }
-
     @action
     handleUsersChange = (selectedOptions: any) => {
         this.selectedUsers = selectedOptions as UserOptions[];
@@ -381,7 +363,7 @@ export default class SharingManager extends React.Component<{}> {
             [
                 {
                     label: 'Individuals',
-                    options: GroupManager.Instance.options.map(({ label, value }) => ({ label, value: "!indType/" + value }))
+                    options: this.users.map(({ user: { email } }) => ({ label: email, value: "!indType/" + email }))
                 },
                 {
                     label: 'Groups',
@@ -390,10 +372,12 @@ export default class SharingManager extends React.Component<{}> {
             ]
             : [];
 
+        console.log(this.users);
+
         const userListContents: (JSX.Element | null)[] = this.users.map(({ user, notificationDoc }) => { // can't use async here
             const userKey = user.email.replace('.', '_');
             // const userKey = user.userDocumentId;
-            const permissions = this.computePermissions(userKey);
+            const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None);
             // const color = ColorMapping.get(permissions);
 
             // console.log(manager);
@@ -401,7 +385,7 @@ export default class SharingManager extends React.Component<{}> {
             // const usersShared = StrCast(metadata?.usersShared, "");
             // console.log(usersShared)
 
-            return permissions === SharingPermissions.None ? null : (
+            return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : (
                 <div
                     key={userKey}
                     className={"container"}
@@ -422,9 +406,24 @@ export default class SharingManager extends React.Component<{}> {
             );
         });
 
+        userListContents.unshift(
+            (
+                <div
+                    key={"owner"}
+                    className={"container"}
+                >
+                    <span className={"padding"}>{this.targetDoc?.author}</span>
+                    <div className="edit-actions">
+                        <div className={"permissions-dropdown"}>
+                            Owner
+                        </div>
+                    </div>
+                </div>
+            )
+        );
 
         const groupListContents = GroupManager.Instance?.getAllGroups().map(group => {
-            const permissions = this.computePermissions(StrCast(group.groupName));
+            const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None);
             // const color = ColorMapping.get(permissions);
 
             return permissions === SharingPermissions.None ? null : (
@@ -492,7 +491,7 @@ export default class SharingManager extends React.Component<{}> {
                 <div className="sharing-contents">
                     <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
                     <div className={"close-button"} onClick={this.close}>
-                        <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
+                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
                     </div>
                     {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
                         :
@@ -516,6 +515,7 @@ export default class SharingManager extends React.Component<{}> {
                     }
                     <div className="main-container">
                         <div className={"individual-container"}>
+                            <div>Individuals</div>
                             <div className={"users-list"} style={{ display: displayUserList ? "flex" : "block" }}>{/*200*/}
                                 {
                                     displayUserList ?
@@ -530,6 +530,7 @@ export default class SharingManager extends React.Component<{}> {
                             </div>
                         </div>
                         <div className={"group-container"}>
+                            <div>Groups</div>
                             <div className={"groups-list"} style={{ display: displayGroupList ? "flex" : "block" }}>{/*200*/}
                                 {
                                     displayGroupList ?
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 0b73a6ad7..249715511 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -21,7 +21,7 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps>
         const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
         const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
         return !p.isDisplayed ? (null) : (
-            <div style={{ pointerEvents: p.isDisplayed ? p.interactive ? "all" : "none" : "none" }}>
+            <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}>
                 <div
                     className={"dialogue-box"}
                     style={{
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ebfc3933a..006129730 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -160,9 +160,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
         }
         return aclPresent ? effectiveAcl : AclEdit;
     }
-    else {
-        return AclEdit;
-    }
+    return AclEdit;
 }
 
 
-- 
cgit v1.2.3-70-g09d2


From 9c2410571d08674477eced80419585ebd15faaf0 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Wed, 15 Jul 2020 16:49:56 +0530
Subject: ui + sorting

---
 src/client/util/GroupManager.scss    | 99 ++++++++++++++++++++----------------
 src/client/util/GroupManager.tsx     | 74 +++++++++++++++++++--------
 src/client/util/GroupMemberView.scss | 27 ++++++++--
 src/client/util/GroupMemberView.tsx  | 21 ++++++--
 src/client/util/SharingManager.scss  | 23 +++++++--
 src/client/util/SharingManager.tsx   | 68 +++++++++++++++++--------
 6 files changed, 215 insertions(+), 97 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 2da1f0d95..8a2c616b1 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -20,9 +20,10 @@
                 // border: none;
                 padding: 8px;
                 min-width: 100%;
-                margin: 4px 0 4px 0;
+                // margin: 4px 0 4px 0;
                 border: 1px solid hsl(0, 0%, 80%);
                 outline: none;
+                height: 30;
 
                 &:focus {
                     // border: unset;
@@ -100,59 +101,69 @@
         }
     }
 
+    .main-container {
+        display: flex;
+        flex-direction: column;
 
-    .group-body {
-        // display: flex;
-        justify-content: space-between;
-        // max-height: 80%;
-        height: 100%;
-        background-color: #e8e8e8;
-        // flex-direction: column;
+        .sort-groups {
+            text-align: left;
+            margin-left: 5;
+            cursor: pointer;
+        }
 
-        // padding-left: 1em;
-        padding-right: 1em;
-        justify-content: space-around;
-        text-align: left;
+        .group-body {
+            // display: flex;
+            justify-content: space-between;
+            // max-height: 80%;
+            height: 220;
+            background-color: #e8e8e8;
+            // flex-direction: column;
+
+            // padding-left: 1em;
+            padding-right: 1em;
+            justify-content: space-around;
+            text-align: left;
 
-        overflow-y: auto;
-        width: 100%;
+            overflow-y: auto;
+            width: 100%;
 
-        .group-row {
-            display: flex;
-            // position: relative;
-            margin-bottom: 5px;
-            min-height: 30px;
-            // border: 1px solid;
-            // border-radius: 10px;
-            align-items: center;
-
-            .group-name {
+            .group-row {
+                display: flex;
                 // position: relative;
-                max-width: 65%;
-                // left: 10;
-                margin: 0 10;
-                color: black;
-            }
+                margin-bottom: 5px;
+                min-height: 30px;
+                // border: 1px solid;
+                // border-radius: 10px;
+                align-items: center;
+
+                .group-name {
+                    // position: relative;
+                    max-width: 65%;
+                    // left: 10;
+                    margin: 0 10;
+                    color: black;
+                }
 
-            .group-info {
-                cursor: pointer;
+                .group-info {
+                    cursor: pointer;
+                }
+
+                button {
+                    position: absolute;
+                    width: 30%;
+                    right: 2;
+                    margin-top: 0;
+                }
             }
 
-            button {
-                position: absolute;
-                width: 30%;
-                right: 2;
-                margin-top: 0;
+            input {
+                border-radius: 5px;
+                border: none;
+                padding: 4px;
+                min-width: 100%;
+                margin: 2px 0;
             }
-        }
 
-        input {
-            border-radius: 5px;
-            border: none;
-            padding: 4px;
-            min-width: 100%;
-            margin: 2px 0;
         }
-
     }
 }
\ No newline at end of file
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index f2b502ae0..2d8930660 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -38,31 +38,38 @@ export default class GroupManager extends React.Component<{}> {
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
     private currentUserGroups: string[] = [];
     @observable private buttonColour: "#979797" | "black" = "#979797";
+    @observable private groupSort: "ascending" | "descending" | "none" = "none";
+
 
     constructor(props: Readonly<{}>) {
         super(props);
         GroupManager.Instance = this;
     }
 
+    componentDidMount() {
+        this.populateUsers();
+    }
+
     /**
      * Fetches the list of users stored on the database.
      */
     populateUsers = async () => {
+        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 isCandidate = user.email !== Doc.CurrentUserEmail;
-            if (isCandidate) {
-                const userDocument = await DocServer.GetRefField(user.userDocumentId);
-                if (userDocument instanceof Doc) {
-                    const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc);
-                    runInAction(() => {
-                        if (notificationDoc instanceof Doc) {
-                            this.users.push(user.email);
-                        }
-                    });
-                }
+            // const isCandidate = user.email !== Doc.CurrentUserEmail;
+            // if (isCandidate) {
+            const userDocument = await DocServer.GetRefField(user.userDocumentId);
+            if (userDocument instanceof Doc) {
+                const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc);
+                runInAction(() => {
+                    if (notificationDoc instanceof Doc) {
+                        this.users.push(user.email);
+                    }
+                });
             }
+            // }
         });
         return Promise.all(evaluating);
     }
@@ -99,7 +106,7 @@ export default class GroupManager extends React.Component<{}> {
     close = () => {
         this.isOpen = false;
         this.currentGroup = undefined;
-        this.users = [];
+        // this.users = [];
         this.createGroupModalOpen = false;
     }
 
@@ -304,6 +311,7 @@ export default class GroupManager extends React.Component<{}> {
                     </div>
                 </div>
                 <input
+                    className="group-input"
                     ref={this.inputRef}
                     onKeyDown={this.handleKeyDown}
                     type="text"
@@ -357,6 +365,17 @@ export default class GroupManager extends React.Component<{}> {
      * A getter that @returns the main interface for the GroupManager.
      */
     private get groupInterface() {
+
+        const sortGroups = (d1: Doc, d2: Doc) => {
+            const g1 = StrCast(d1.groupName);
+            const g2 = StrCast(d2.groupName);
+
+            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;
+
         return (
             <div className="group-interface">
                 {this.groupCreationModal}
@@ -375,19 +394,30 @@ export default class GroupManager extends React.Component<{}> {
                         <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
                     </div>
                 </div>
-                <div className="group-body">
-                    {this.getAllGroups().map(group =>
-                        <div className="group-row">
-                            <div className="group-name" >{group.groupName}</div>
-                            <div className="group-info" onClick={action(() => this.currentGroup = group)}>
-                                <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
-                            </div>
-                            {/* <button onClick={action(() => this.currentGroup = group)}>
+                <div className="main-container">
+                    <div
+                        className="sort-groups"
+                        onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
+                        Name {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
+                    </div>
+                    <div className="group-body">
+                        {groups.map(group =>
+                            <div
+                                className="group-row"
+                                key={StrCast(group.groupName)}
+                            >
+                                <div className="group-name" >{group.groupName}</div>
+                                <div className="group-info" onClick={action(() => this.currentGroup = group)}>
+                                    <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+                                </div>
+                                {/* <button onClick={action(() => this.currentGroup = group)}>
                                 {this.hasEditAccess(group) ? "Edit" : "View"}
                             </button> */}
-                        </div>
-                    )}
+                            </div>
+                        )}
+                    </div>
                 </div>
+
             </div>
         );
     }
diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss
index 9bdf832e0..a34e5b989 100644
--- a/src/client/util/GroupMemberView.scss
+++ b/src/client/util/GroupMemberView.scss
@@ -8,7 +8,7 @@
     // color: black;
 
     hr {
-        margin-top: 10;
+        margin-top: 20;
     }
 
     button {
@@ -17,7 +17,7 @@
         border-radius: 5px;
         border: 0px;
         color: #fcfbf7;
-        // text-transform: uppercase;
+        text-transform: none;
         letter-spacing: 2px;
         font-size: 75%;
         padding: 10px;
@@ -45,15 +45,33 @@
             outline: none;
             color: black;
             margin-top: -5;
+            height: 20;
+            text-overflow: ellipsis;
+
+            &:hover {
+                text-overflow: visible;
+                overflow-x: auto;
+            }
+        }
+
+        .sort-emails {
+            float: left;
+            margin: -18 0 0 5;
+            cursor: pointer;
         }
 
         .group-buttons {
             display: flex;
             margin-top: 5;
+            margin-bottom: 25;
 
             .add-member-dropdown {
                 width: 65%;
                 margin: 0 5;
+
+                input {
+                    height: 30;
+                }
             }
         }
     }
@@ -61,7 +79,7 @@
     .editing-contents {
         overflow-y: auto;
         // max-height: 67%;
-        height: 67%;
+        height: 65%;
         width: 100%;
         color: black;
         margin-top: -15px;
@@ -70,6 +88,7 @@
             display: flex;
             align-items: center;
             margin-bottom: 10px;
+            position: relative;
             // border: 1px solid;
             // border-radius: 10px;
 
@@ -83,7 +102,7 @@
 
             .remove-button {
                 position: absolute;
-                right: 30;
+                right: 10;
                 cursor: pointer;
             }
         }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 2462ee4d5..ebe9830ba 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
 import GroupManager, { UserOptions } from "./GroupManager";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { StrCast } from "../../fields/Types";
-import { action } from "mobx";
+import { action, observable } from "mobx";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import Select from "react-select";
@@ -21,10 +21,17 @@ interface GroupMemberViewProps {
 @observer
 export default class GroupMemberView extends React.Component<GroupMemberViewProps> {
 
+    @observable private memberSort: "ascending" | "descending" | "none" = "none";
 
     private get editingInterface() {
-        const members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
+        let members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
+        members = this.memberSort === "ascending" ? members.sort() : this.memberSort === "descending" ? members.sort().reverse() : members;
+
         const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
+        console.log(this.props.group, options);
+        console.log(GroupManager.Instance.options);
+
+
         return (!this.props.group ? null :
             <div className="editing-interface">
                 <div className="editing-header">
@@ -59,11 +66,19 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                             <button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button>
                         </div> :
                         null}
+                    <div
+                        className="sort-emails"
+                        onClick={action(() => this.memberSort = this.memberSort === "ascending" ? "descending" : this.memberSort === "descending" ? "none" : "ascending")}>
+                        Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */}
+                    </div>
                 </div>
                 <hr />
                 <div className="editing-contents">
                     {members.map(member => (
-                        <div className="editing-row">
+                        <div
+                            className="editing-row"
+                            key={member}
+                        >
                             <div className="user-email">
                                 {member}
                             </div>
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index ce23ce413..572b94ffb 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -1,4 +1,4 @@
-@import "../views/globalCssVariables";
+// @import "../views/globalCssVariables";
 
 .sharing-interface {
     // display: flex;
@@ -40,6 +40,10 @@
 
             .user-search {
                 width: 90%;
+
+                input {
+                    height: 30;
+                }
             }
 
             .permissions-select {
@@ -68,6 +72,12 @@
                 display: flex;
                 flex-direction: column;
 
+                .user-sort {
+                    text-align: left;
+                    margin-left: 10;
+                    cursor: pointer;
+                }
+
                 .share-title {
                     margin-top: 20px;
                     margin-bottom: 20px;
@@ -103,12 +113,12 @@
         }
 
         button {
-            background: $darker-alt-accent;
+            // background: $darker-alt-accent;
             outline: none;
             border-radius: 5px;
             border: 0px;
             color: #fcfbf7;
-            text-transform: uppercase;
+            text-transform: none;
             letter-spacing: 2px;
             font-size: 75%;
             padding: 0 10;
@@ -173,6 +183,10 @@
         padding: 0;
         align-items: center;
 
+        .group-info {
+            cursor: pointer;
+        }
+
         &:hover .padding {
             white-space: unset;
         }
@@ -183,12 +197,13 @@
             text-overflow: ellipsis;
             overflow: hidden;
             white-space: nowrap;
-            max-width: 48%;
+            max-width: 40%;
         }
 
         .permissions-dropdown {
             border: none;
             height: 25;
+            background-color: #e8e8e8;
         }
 
         .edit-actions {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6c7c634eb..817b7c6b8 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -75,6 +75,9 @@ export default class SharingManager extends React.Component<{}> {
     @observable private overlayOpacity = 0.4;
     @observable private selectedUsers: UserOptions[] | null = null;
     @observable private permissions: SharingPermissions = SharingPermissions.Edit;
+    @observable private individualSort: "ascending" | "descending" | "none" = "none";
+    @observable private groupSort: "ascending" | "descending" | "none" = "none";
+
 
     // private get linkVisible() {
     //     return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
@@ -151,8 +154,6 @@ export default class SharingManager extends React.Component<{}> {
         // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
 
         users.forEach(({ notificationDoc }) => {
-
-            DocListCastAsync(notificationDoc[storage]).then(res => console.log(res));
             DocListCastAsync(notificationDoc[storage]).then(resolved => {
                 if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
                 else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
@@ -353,28 +354,44 @@ export default class SharingManager extends React.Component<{}> {
         this.selectedUsers = null;
     }
 
+    sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
+        const { email: e1 } = u1.user;
+        const { email: e2 } = u2.user;
+        return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
+    }
+
+    sortGroups = (group1: Doc, group2: Doc) => {
+        const g1 = StrCast(group1.groupName);
+        const g2 = StrCast(group2.groupName);
+        return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
+    }
+
     private get sharingInterface() {
-        const existOtherUsers = this.users.length > 0;
-        const existGroups = GroupManager.Instance?.getAllGroups().length > 0;
 
-        // const manager = this.sharingDoc!;
+        const groupList = GroupManager.Instance?.getAllGroups() || [];
+
+        const sortedUsers = this.users.sort(this.sortUsers)
+            .map(({ user: { email } }) => ({ label: email, value: "!indType/" + email }));
+        const sortedGroups = groupList.sort(this.sortGroups)
+            .map(({ groupName }) => ({ label: StrCast(groupName), value: "!groupType/" + StrCast(groupName) }));
 
         const options: GroupOptions[] = GroupManager.Instance ?
             [
                 {
                     label: 'Individuals',
-                    options: this.users.map(({ user: { email } }) => ({ label: email, value: "!indType/" + email }))
+                    options: sortedUsers
                 },
                 {
                     label: 'Groups',
-                    options: GroupManager.Instance.getAllGroups().map(({ groupName }) => ({ label: StrCast(groupName), value: "!groupType/" + StrCast(groupName) }))
+                    options: sortedGroups
                 }
             ]
             : [];
 
-        console.log(this.users);
+        const users = this.individualSort === "ascending" ? this.users.sort(this.sortUsers) : this.individualSort === "descending" ? this.users.sort(this.sortUsers).reverse() : this.users;
+        const groups = this.groupSort === "ascending" ? groupList.sort(this.sortGroups) : this.groupSort === "descending" ? groupList.sort(this.sortGroups).reverse() : groupList;
 
-        const userListContents: (JSX.Element | null)[] = this.users.map(({ user, notificationDoc }) => { // can't use async here
+        const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => { // can't use async here
             const userKey = user.email.replace('.', '_');
             // const userKey = user.userDocumentId;
             const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None);
@@ -422,7 +439,7 @@ export default class SharingManager extends React.Component<{}> {
             )
         );
 
-        const groupListContents = GroupManager.Instance?.getAllGroups().map(group => {
+        const groupListContents = groups.map(group => {
             const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None);
             // const color = ColorMapping.get(permissions);
 
@@ -431,7 +448,10 @@ export default class SharingManager extends React.Component<{}> {
                     key={StrCast(group.groupName)}
                     className={"container"}
                 >
-                    <span className={"padding"}>{group.groupName}</span>
+                    <div className={"padding"}>{group.groupName}</div>
+                    <div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
+                        <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+                    </div>
                     <div className="edit-actions">
                         <select
                             className={"permissions-dropdown"}
@@ -441,14 +461,13 @@ export default class SharingManager extends React.Component<{}> {
                         >
                             {this.sharingOptions}
                         </select>
-                        <button onClick={action(() => GroupManager.Instance.currentGroup = group)}>Edit</button>
                     </div>
                 </div>
             );
         });
 
-        const displayUserList = userListContents?.every(user => user === null);
-        const displayGroupList = groupListContents?.every(group => group === null);
+        const displayUserList = !userListContents?.every(user => user === null);
+        const displayGroupList = !groupListContents?.every(group => group === null);
 
         return (
             <div className={"sharing-interface"}>
@@ -515,10 +534,14 @@ export default class SharingManager extends React.Component<{}> {
                     }
                     <div className="main-container">
                         <div className={"individual-container"}>
-                            <div>Individuals</div>
-                            <div className={"users-list"} style={{ display: displayUserList ? "flex" : "block" }}>{/*200*/}
+                            <div
+                                className="user-sort"
+                                onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
+                                Individuals {this.individualSort === "ascending" ? "↑" : this.individualSort === "descending" ? "↓" : ""} {/* → */}
+                            </div>
+                            <div className={"users-list"} style={{ display: !displayUserList ? "flex" : "block" }}>{/*200*/}
                                 {
-                                    displayUserList ?
+                                    !displayUserList ?
                                         <div
                                             className={"none"}
                                         >
@@ -530,10 +553,15 @@ export default class SharingManager extends React.Component<{}> {
                             </div>
                         </div>
                         <div className={"group-container"}>
-                            <div>Groups</div>
-                            <div className={"groups-list"} style={{ display: displayGroupList ? "flex" : "block" }}>{/*200*/}
+                            <div
+                                className="user-sort"
+                                onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
+                                Groups {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
+
+                            </div>
+                            <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/}
                                 {
-                                    displayGroupList ?
+                                    !displayGroupList ?
                                         <div
                                             className={"none"}
                                         >
-- 
cgit v1.2.3-70-g09d2


From 3ad593cc8865d3fa1dc22bf403ad7cfaf1a751e6 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Wed, 15 Jul 2020 21:52:23 +0530
Subject: bugfix + cleanup

---
 src/client/util/GroupManager.scss       |  25 --------
 src/client/util/GroupManager.tsx        |   4 --
 src/client/util/GroupMemberView.scss    |  10 ---
 src/client/util/GroupMemberView.tsx     |   3 -
 src/client/util/SharingManager.scss     |  30 ---------
 src/client/util/SharingManager.tsx      | 106 +++++---------------------------
 src/client/views/nodes/DocumentView.tsx |  22 +++----
 src/fields/Doc.ts                       |   2 +-
 8 files changed, 28 insertions(+), 174 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 8a2c616b1..34d4f40f8 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,8 +1,4 @@
-// @import "../views/globalCssVariables";
-
 .group-interface {
-    // background-color: whitesmoke !important;
-    // color: grey;
     width: 550px;
     height: 300px;
 
@@ -12,21 +8,17 @@
             flex-direction: column;
             height: 90%;
             justify-content: space-between;
-            // flex-basis: 30%;
             margin-left: 5px;
 
             input {
                 border-radius: 5px;
-                // border: none;
                 padding: 8px;
                 min-width: 100%;
-                // margin: 4px 0 4px 0;
                 border: 1px solid hsl(0, 0%, 80%);
                 outline: none;
                 height: 30;
 
                 &:focus {
-                    // border: unset;
                     border: 2.5px solid #2684FF;
                 }
             }
@@ -43,18 +35,12 @@
         }
     }
 
-    //     .dialogue-box {
-    //     width: 450;
-    //     height: 300;
-    // }
 
     button {
-        // background: $lighter-alt-accent;
         align-self: center;
         outline: none;
         border-radius: 5px;
         border: 0px;
-        // color: #fcfbf7;
         text-transform: none;
         letter-spacing: 2px;
         font-size: 75%;
@@ -94,10 +80,8 @@
         p {
             font-size: 20px;
             text-align: left;
-            // margin: 0 0 20px 0;
             margin-right: 15px;
             color: black;
-            // width: 60%;
         }
     }
 
@@ -112,14 +96,10 @@
         }
 
         .group-body {
-            // display: flex;
             justify-content: space-between;
-            // max-height: 80%;
             height: 220;
             background-color: #e8e8e8;
-            // flex-direction: column;
 
-            // padding-left: 1em;
             padding-right: 1em;
             justify-content: space-around;
             text-align: left;
@@ -129,17 +109,12 @@
 
             .group-row {
                 display: flex;
-                // position: relative;
                 margin-bottom: 5px;
                 min-height: 30px;
-                // border: 1px solid;
-                // border-radius: 10px;
                 align-items: center;
 
                 .group-name {
-                    // position: relative;
                     max-width: 65%;
-                    // left: 10;
                     margin: 0 10;
                     color: black;
                 }
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 2d8930660..12951f2ab 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -286,7 +286,6 @@ export default class GroupManager extends React.Component<{}> {
      */
     @action
     createGroup = () => {
-        // this.createGroupModalOpen = true;
         if (!this.inputRef.current?.value) {
             alert("Please enter a group name");
             return;
@@ -410,9 +409,6 @@ export default class GroupManager extends React.Component<{}> {
                                 <div className="group-info" onClick={action(() => this.currentGroup = group)}>
                                     <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
                                 </div>
-                                {/* <button onClick={action(() => this.currentGroup = group)}>
-                                {this.hasEditAccess(group) ? "Edit" : "View"}
-                            </button> */}
                             </div>
                         )}
                     </div>
diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss
index a34e5b989..c609c5c7b 100644
--- a/src/client/util/GroupMemberView.scss
+++ b/src/client/util/GroupMemberView.scss
@@ -1,18 +1,12 @@
-// @import "../views/globalCssVariables";
-
 .editing-interface {
-    // background-color: whitesmoke !important;
-    // color: grey;
     width: 100%;
     height: 100%;
-    // color: black;
 
     hr {
         margin-top: 20;
     }
 
     button {
-        // background: $darker-alt-accent;
         outline: none;
         border-radius: 5px;
         border: 0px;
@@ -78,7 +72,6 @@
 
     .editing-contents {
         overflow-y: auto;
-        // max-height: 67%;
         height: 65%;
         width: 100%;
         color: black;
@@ -89,11 +82,8 @@
             align-items: center;
             margin-bottom: 10px;
             position: relative;
-            // border: 1px solid;
-            // border-radius: 10px;
 
             .user-email {
-                // position: relative;
                 min-width: 65%;
                 word-break: break-all;
                 padding: 0 5;
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index ebe9830ba..f20670c4e 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -28,9 +28,6 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
         members = this.memberSort === "ascending" ? members.sort() : this.memberSort === "descending" ? members.sort().reverse() : members;
 
         const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
-        console.log(this.props.group, options);
-        console.log(GroupManager.Instance.options);
-
 
         return (!this.props.group ? null :
             <div className="editing-interface">
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 572b94ffb..130785672 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -1,16 +1,7 @@
-// @import "../views/globalCssVariables";
-
 .sharing-interface {
-    // display: flex;
-    // flex-direction: column;
     width: 600px;
     height: 360px;
 
-    // .dialogue-box {
-    //     width: 450;
-    //     height: 300;
-    // }
-
     .overlay {
         transform: translate(-20px, -20px);
     }
@@ -87,7 +78,6 @@
                 .users-list {
                     font-style: italic;
                     background: #e8e8e8;
-                    // border: 1px solid black;
                     padding-left: 10px;
                     padding-right: 10px;
                     overflow-y: scroll;
@@ -98,7 +88,6 @@
                     align-items: center;
                     text-align: center;
                     justify-content: center;
-                    // color: red;
                     color: black;
                     height: 250px;
                     margin: 0 2;
@@ -113,7 +102,6 @@
         }
 
         button {
-            // background: $darker-alt-accent;
             outline: none;
             border-radius: 5px;
             border: 0px;
@@ -135,8 +123,6 @@
     p {
         font-size: 20px;
         text-align: left;
-        // font-style: italic;
-        // padding: 0;
         margin: 0 0 20px 0;
         color: black;
     }
@@ -174,8 +160,6 @@
         -ms-user-select: none;
         user-select: none;
         width: 100%;
-        // min-width: 700px;
-        // max-width: 700px;
         text-align: left;
         font-style: normal;
         font-size: 14;
@@ -249,18 +233,4 @@
             padding-top: 12px;
         }
     }
-
-    // .close-button {
-    //     border-radius: 5px;
-    //     margin-top: 20px;
-    //     padding: 10px 0;
-    //     background: aliceblue;
-    //     transition: 0.5s ease all;
-    //     border: 1px solid;
-    //     border-color: aliceblue;
-    // }
-
-    // .close-button:hover {
-    //     border-color: black;
-    // }
 }
\ No newline at end of file
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index be86b183f..8d4e508ac 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -36,13 +36,6 @@ export enum SharingPermissions {
     None = "Not Shared"
 }
 
-// const ColorMapping = new Map<string, string>([
-//     [SharingPermissions.None, "red"],
-//     [SharingPermissions.View, "maroon"],
-//     [SharingPermissions.Add, "blue"],
-//     [SharingPermissions.Edit, "green"]
-// ]);
-
 interface GroupOptions {
     label: string;
     options: UserOptions[];
@@ -67,10 +60,9 @@ export default class SharingManager extends React.Component<{}> {
     public static Instance: SharingManager;
     @observable private isOpen = false;
     @observable private users: ValidatedUser[] = [];
-    // @observable private groups: Doc[] = [];
     @observable private targetDoc: Doc | undefined;
     @observable private targetDocView: DocumentView | undefined;
-    @observable private copied = false;
+    // @observable private copied = false;
     @observable private dialogueBoxOpacity = 1;
     @observable private overlayOpacity = 0.4;
     @observable private selectedUsers: UserOptions[] | null = null;
@@ -90,32 +82,20 @@ export default class SharingManager extends React.Component<{}> {
             this.targetDoc = target.props.Document;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = true;
-            // if (!this.sharingDoc) {
-            //     this.sharingDoc = new Doc;
-            // }
         }));
 
-        // runInAction(() => this.groups = GroupManager.Instance.getAllGroups());
     }
 
     public close = action(() => {
         this.isOpen = false;
         this.users = [];
         setTimeout(action(() => {
-            this.copied = false;
+            // this.copied = false;
             DictationOverlay.Instance.hasActiveModal = false;
             this.targetDoc = undefined;
         }), 500);
     });
 
-    // private get sharingDoc() {
-    //     return this.targetDoc ? Cast(this.targetDoc[SharingKey], Doc) as Doc : undefined;
-    // }
-
-    // private set sharingDoc(value: Doc | undefined) {
-    //     this.targetDoc && (this.targetDoc[SharingKey] = value);
-    // }
-
     constructor(props: {}) {
         super(props);
         SharingManager.Instance = this;
@@ -152,7 +132,6 @@ export default class SharingManager extends React.Component<{}> {
         Doc.GetProto(target)[ACL] = permission;
 
         group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
-        // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
 
         users.forEach(({ notificationDoc }) => {
             DocListCastAsync(notificationDoc[storage]).then(resolved => {
@@ -171,7 +150,6 @@ export default class SharingManager extends React.Component<{}> {
                     DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
                 });
             });
-            // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
         }
     }
 
@@ -184,7 +162,6 @@ export default class SharingManager extends React.Component<{}> {
                     DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
                 });
             });
-            // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
         }
     }
 
@@ -208,14 +185,10 @@ export default class SharingManager extends React.Component<{}> {
     setInternalSharing = (recipient: ValidatedUser, permission: string) => {
         const { user, notificationDoc } = recipient;
         const target = this.targetDoc!;
-        // const manager = this.sharingDoc!;
         const key = user.email.replace('.', '_');
-        // const key = user.userDocumentId;
 
         const ACL = `ACL-${key}`;
 
-        // const permissions: { [key: string]: number } = target[ACL] ? JSON.parse(StrCast(target[ACL])) : {};
-
         target[ACL] = permission;
         Doc.GetProto(target)[ACL] = permission;
 
@@ -228,47 +201,12 @@ export default class SharingManager extends React.Component<{}> {
         }
         else {
             DocListCastAsync(notificationDoc[storage]).then(resolved => {
-                Doc.IndexOf(target, resolved!) === -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+                Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
             });
         }
 
     }
 
-
-    // let metadata = await DocCastAsync(manager[key]);
-    // const permissions: { [key: string]: number } = metadata?.permissions ? JSON.parse(StrCast(metadata.permissions)) : {};
-    // permissions[StrCast(group ? group.groupName : Doc.CurrentUserEmail)] = parseInt(HierarchyMapping.get(permission)!);
-    // const max = Math.max(...Object.values(permissions));
-
-    // switch (max) {
-    //     case 0:
-    //         // if (metadata) {
-    //         //     const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
-    //         //     Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
-    //         //     manager[key] = undefined;
-    //         // }
-    //         Doc.RemoveDocFromList(notificationDoc, storage, target);
-    //         break;
-
-    //     case 1: case 2: case 3:
-
-    //         Doc.AddDocToList(notificationDoc, storage, target);
-
-    //         if (!metadata) {
-    //             metadata = new Doc;
-    //             const sharedAlias = Doc.MakeAlias(target);
-    //             Doc.AddDocToList(notificationDoc, storage, target);
-    //             metadata.sharedAlias = sharedAlias;
-    //             manager[key] = metadata;
-    //         }
-    //         metadata.permissions = JSON.stringify(permissions);
-    //         // metadata.usersShared = JSON.stringify(keys);
-    //         break;
-    // }
-
-    // if (metadata) metadata.maxPermission = HierarchyMapping.get(`${max}`);
-
-
     // private setExternalSharing = (permission: string) => {
     //     const sharingDoc = this.sharingDoc;
     //     if (!sharingDoc) {
@@ -277,20 +215,20 @@ export default class SharingManager extends React.Component<{}> {
     //     sharingDoc[PublicKey] = permission;
     // }
 
-    private get sharingUrl() {
-        if (!this.targetDoc) {
-            return undefined;
-        }
-        const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
-        return `${baseUrl}?sharing=true`;
-    }
+    // private get sharingUrl() {
+    //     if (!this.targetDoc) {
+    //         return undefined;
+    //     }
+    //     const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
+    //     return `${baseUrl}?sharing=true`;
+    // }
 
-    copy = action(() => {
-        if (this.sharingUrl) {
-            Utils.CopyText(this.sharingUrl);
-            this.copied = true;
-        }
-    });
+    // copy = action(() => {
+    //     if (this.sharingUrl) {
+    //         Utils.CopyText(this.sharingUrl);
+    //         this.copied = true;
+    //     }
+    // });
 
     private get sharingOptions() {
         return Object.values(SharingPermissions).map(permission => {
@@ -394,14 +332,7 @@ export default class SharingManager extends React.Component<{}> {
 
         const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => { // can't use async here
             const userKey = user.email.replace('.', '_');
-            // const userKey = user.userDocumentId;
             const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None);
-            // const color = ColorMapping.get(permissions);
-
-            // console.log(manager);
-            // const metadata = manager[userKey] as Doc;
-            // const usersShared = StrCast(metadata?.usersShared, "");
-            // console.log(usersShared)
 
             return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : (
                 <div
@@ -409,12 +340,10 @@ export default class SharingManager extends React.Component<{}> {
                     className={"container"}
                 >
                     <span className={"padding"}>{user.email}</span>
-                    {/* <div className={"shared-by"}>{usersShared}</div> */}
                     <div className="edit-actions">
                         <select
                             className={"permissions-dropdown"}
                             value={permissions}
-                            // style={{ color, borderColor: color }}
                             onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
                         >
                             {this.sharingOptions}
@@ -442,7 +371,6 @@ export default class SharingManager extends React.Component<{}> {
 
         const groupListContents = groups.map(group => {
             const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None);
-            // const color = ColorMapping.get(permissions);
 
             return permissions === SharingPermissions.None ? null : (
                 <div
@@ -457,7 +385,6 @@ export default class SharingManager extends React.Component<{}> {
                         <select
                             className={"permissions-dropdown"}
                             value={permissions}
-                            // style={{ color, borderColor: color }}
                             onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
                         >
                             {this.sharingOptions}
@@ -582,7 +509,6 @@ export default class SharingManager extends React.Component<{}> {
     }
 
     render() {
-        // console.log(this.sharingDoc);
         return (
             <MainViewModal
                 contents={this.sharingInterface}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0015e4bc7..8ef367a7c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -831,17 +831,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
-        const existingAcls = cm.findByDescription("Privacy...");
-        const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
-        aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
-
-        cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
+        // const existingAcls = cm.findByDescription("Privacy...");
+        // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
+        // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
+        // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
+        // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
+        // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
+        // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
+        // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
+        // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+
+        // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8ab4735a7..5dfc14a4a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -109,7 +109,7 @@ const AclMap = new Map<string, symbol>([
 ]);
 
 export function fetchProto(doc: Doc) {
-    if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
+    if (doc.author !== Doc.CurrentUserEmail) {
         untracked(() => {
             const permissions: { [key: string]: symbol } = {};
 
-- 
cgit v1.2.3-70-g09d2


From 7b7d83f6f7070334fdfc4dd7cca03699b8e21078 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Mon, 20 Jul 2020 19:21:54 +0530
Subject: fixed some addonly stuff + added annotations to playground fields +
 recursively distributes acls + some modal stuff

---
 src/client/apis/GoogleAuthenticationManager.tsx    |  7 +--
 .../apis/HypothesisAuthenticationManager.tsx       |  6 ++-
 src/client/util/GroupManager.scss                  |  2 +-
 src/client/util/GroupManager.tsx                   |  6 +--
 src/client/util/SettingsManager.tsx                |  4 +-
 src/client/util/SharingManager.tsx                 | 58 ++++++++++++++++++----
 src/client/views/DocComponent.tsx                  | 26 ++++++++--
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/CollectionView.tsx    | 25 +++++++++-
 src/fields/Doc.ts                                  | 14 +++---
 src/fields/util.ts                                 | 21 ++++----
 11 files changed, 128 insertions(+), 43 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 5a2bdb13b..117d1fa1e 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -146,7 +146,7 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
 
     private get dialogueBoxStyle() {
         const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
-        return { borderColor, transition: "0.2s borderColor ease" };
+        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
     }
 
     render() {
@@ -155,9 +155,10 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
                 isDisplayed={this.openState}
                 interactive={true}
                 contents={this.renderPrompt}
-                overlayDisplayedOpacity={0.9}
+                // overlayDisplayedOpacity={0.9}
                 dialogueBoxStyle={this.dialogueBoxStyle}
-                closeOnExternalClick={() => this.isOpen = false}
+                overlayStyle={{ zIndex: 1001 }}
+                closeOnExternalClick={action(() => this.isOpen = false)}
             />
         );
     }
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
index a7fcf86a4..c3e8d2fff 100644
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -138,7 +138,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
 
     private get dialogueBoxStyle() {
         const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
-        return { borderColor, transition: "0.2s borderColor ease" };
+        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
     }
 
     render() {
@@ -147,8 +147,10 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
                 isDisplayed={this.openState}
                 interactive={true}
                 contents={this.renderPrompt}
-                overlayDisplayedOpacity={0.9}
+                // overlayDisplayedOpacity={0.9}
                 dialogueBoxStyle={this.dialogueBoxStyle}
+                overlayStyle={{ zIndex: 1001 }}
+                closeOnExternalClick={action(() => this.isOpen = false)}
             />
         );
     }
diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 34d4f40f8..51e4fa9e2 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,5 +1,5 @@
 .group-interface {
-    width: 550px;
+    width: 380px;
     height: 300px;
 
     .dialogue-box {
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 12951f2ab..2e5ecc543 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -354,7 +354,7 @@ export default class GroupManager extends React.Component<{}> {
                 isDisplayed={this.createGroupModalOpen}
                 interactive={true}
                 contents={contents}
-                dialogueBoxStyle={{ width: "70%", height: "70%" }}
+                dialogueBoxStyle={{ width: "90%", height: "70%" }}
                 closeOnExternalClick={action(() => this.createGroupModalOpen = false)}
             />
         );
@@ -424,8 +424,8 @@ export default class GroupManager extends React.Component<{}> {
                 contents={this.groupInterface}
                 isDisplayed={this.isOpen}
                 interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
+                dialogueBoxStyle={{ zIndex: 1002 }}
+                overlayStyle={{ zIndex: 1001 }}
                 closeOnExternalClick={this.close}
             />
         );
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index fc5fdd869..90d59aa51 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -119,10 +119,10 @@ export default class SettingsManager extends React.Component<{}> {
                         <button onClick={this.togglePlaygroundMode}>{`${this.playgroundMode ? "Disable" : "Enable"} playground mode`}</button>
                         <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
                         <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
+                        <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
                         <button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
                             {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                         </button>
-                        <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
                     </div>
                     {this.settingsContent === "password" ?
                         <div className="settings-content">
@@ -155,8 +155,6 @@ export default class SettingsManager extends React.Component<{}> {
                 contents={this.settingsInterface}
                 isDisplayed={this.isOpen}
                 interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
                 closeOnExternalClick={this.close}
             />
         );
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fe7324d5c..8b3ac2613 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,13 +1,12 @@
 import { observable, runInAction, action } from "mobx";
 import * as React from "react";
 import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocListCastAsync } from "../../fields/Doc";
+import { Doc, Opt, DocListCastAsync, DataSym, DocListCast } from "../../fields/Doc";
 import { DocServer } from "../DocServer";
 import { Cast, StrCast } from "../../fields/Types";
 import * as RequestPromise from "request-promise";
 import { Utils } from "../../Utils";
 import "./SharingManager.scss";
-import { Id } from "../../fields/FieldSymbols";
 import { observer } from "mobx-react";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
@@ -82,6 +81,7 @@ export default class SharingManager extends React.Component<{}> {
             this.targetDoc = target.props.Document;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = true;
+            this.permissions = SharingPermissions.Edit;
         }));
 
     }
@@ -127,9 +127,11 @@ export default class SharingManager extends React.Component<{}> {
 
         const target = this.targetDoc!;
         const ACL = `ACL-${StrCast(group.groupName)}`;
+        // fix this - not needed (here and setinternalsharing and removegroup)
+        // target[ACL] = permission;
+        // Doc.GetProto(target)[ACL] = permission;
 
-        target[ACL] = permission;
-        Doc.GetProto(target)[ACL] = permission;
+        this.distributeAcls(ACL, permission as SharingPermissions);
 
         group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
 
@@ -170,7 +172,9 @@ export default class SharingManager extends React.Component<{}> {
             DocListCastAsync(group.docsShared).then(resolved => {
                 resolved?.forEach(doc => {
                     const ACL = `ACL-${StrCast(group.groupName)}`;
-                    doc[ACL] = "Not Shared";
+                    // doc[ACL] = doc[DataSym][ACL] = "Not Shared";
+
+                    this.distributeAcls(ACL, SharingPermissions.None, doc);
 
                     const members: string[] = JSON.parse(StrCast(group.members));
                     const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -189,8 +193,10 @@ export default class SharingManager extends React.Component<{}> {
 
         const ACL = `ACL-${key}`;
 
-        target[ACL] = permission;
-        Doc.GetProto(target)[ACL] = permission;
+        // target[ACL] = permission;
+        // Doc.GetProto(target)[ACL] = permission;
+
+        this.distributeAcls(ACL, permission as SharingPermissions);
 
         if (permission !== SharingPermissions.None) {
             DocListCastAsync(notificationDoc[storage]).then(resolved => {
@@ -202,6 +208,40 @@ export default class SharingManager extends React.Component<{}> {
                 Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
             });
         }
+    }
+
+    @action
+    distributeAcls = (key: string, acl: SharingPermissions, doc?: Doc) => {
+        const target = doc ? doc : this.targetDoc!;
+        const dataDoc = target[DataSym];
+        target[key] = acl;
+        if (dataDoc) dataDoc[key] = acl;
+        // dataDoc[key] = target[key] = acl;
+        // next line distributes the acl to all children of the target
+        DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
+            if (d.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, d);
+                d[key] = acl;
+            }
+            const data = d[DataSym];
+            if (data && data.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, data);
+                data[key] = acl;
+            }
+        });
+
+        DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
+            if (d.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, d);
+                d[key] = acl;
+            }
+            const data = d[DataSym];
+            if (data && data.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, data);
+                data[key] = acl;
+            }
+            console.log(d, d[DataSym]);
+        });
 
     }
 
@@ -308,9 +348,9 @@ export default class SharingManager extends React.Component<{}> {
         const groupList = GroupManager.Instance?.getAllGroups() || [];
 
         const sortedUsers = this.users.sort(this.sortUsers)
-            .map(({ user: { email } }) => ({ label: email, value: "!indType/" + email }));
+            .map(({ user: { email } }) => ({ label: email, value: indType + email }));
         const sortedGroups = groupList.sort(this.sortGroups)
-            .map(({ groupName }) => ({ label: StrCast(groupName), value: "!groupType/" + StrCast(groupName) }));
+            .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
 
         const options: GroupOptions[] = GroupManager.Instance ?
             [
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index eb58d8a3e..8740d17c2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly } from '../../fields/Doc';
+import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym } from '../../fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
 import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -7,7 +7,8 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl } from '../../fields/util';
+import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util';
+import { SharingPermissions } from '../util/SharingManager';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -92,6 +93,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
+        private AclMap = new Map<symbol, string>([
+            [AclPrivate, SharingPermissions.None],
+            [AclReadonly, SharingPermissions.View],
+            [AclAddonly, SharingPermissions.Add],
+            [AclEdit, SharingPermissions.Edit]
+        ]);
+
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
         styleFromLayoutString = (scale: number) => {
@@ -139,11 +147,21 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+
+            if (this.props.Document[AclSym]) {
+                added.forEach(d => {
+                    const dataDoc = d[DataSym];
+                    dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+                    for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+                        dataDoc[key] = d[key] = this.AclMap.get(value);
+                    }
+                });
+            }
             if (added.length) {
-                if (effectiveAcl === AclReadonly) {
+                if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
                     return false;
                 } else if (effectiveAcl === AclAddonly) {
-                    added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
+                    added.map(doc => console.log(Doc.AddDocToList(targetDataDoc, this.annotationKey, doc)));
                 } else {
                     added.map(doc => doc.context = this.props.Document);
                     targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5c6781f4c..61d2246db 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -91,7 +91,7 @@ export class MainView extends React.Component {
     public isPointerDown = false;
 
     componentDidMount() {
-        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus", "data-annotations"]); // can play with these fields on someone else's
 
         const tag = document.createElement('script');
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 50d66c567..17567ea73 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -48,6 +48,7 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import CollectionMenu from './CollectionMenu';
+import { SharingPermissions } from '../../util/SharingManager';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -106,6 +107,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
+    private AclMap = new Map<symbol, string>([
+        [AclPrivate, SharingPermissions.None],
+        [AclReadonly, SharingPermissions.View],
+        [AclAddonly, SharingPermissions.Add],
+        [AclEdit, SharingPermissions.Edit]
+    ]);
+
     get collectionViewType(): CollectionViewType | undefined {
         const viewField = StrCast(this.props.Document._viewType);
         if (CollectionView._safeMode) {
@@ -128,11 +136,26 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         if (this.props.filterAddDocument?.(doc) === false) {
             return false;
         }
+
         const docs = doc instanceof Doc ? [doc] : doc;
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
         const effectiveAcl = GetEffectiveAcl(this.props.Document);
+        if (this.props.Document[AclSym]) {
+            // change so it only adds if more restrictive
+            added.forEach(d => {
+                console.log(d[Id]);
+                const dataDoc = d[DataSym];
+                console.log(dataDoc[Id]);
+                for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+                    dataDoc[key] = d[key] = this.AclMap.get(value);
+                }
+                dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+
+            });
+        }
+
         if (added.length) {
             if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
                 return false;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5dfc14a4a..ef57171bf 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -109,15 +109,15 @@ const AclMap = new Map<string, symbol>([
 ]);
 
 export function fetchProto(doc: Doc) {
-    if (doc.author !== Doc.CurrentUserEmail) {
-        untracked(() => {
-            const permissions: { [key: string]: symbol } = {};
+    // if (doc.author !== Doc.CurrentUserEmail) {
+    untracked(() => {
+        const permissions: { [key: string]: symbol } = {};
 
-            Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
+        Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
 
-            if (Object.keys(permissions).length) doc[AclSym] = permissions;
-        });
-    }
+        if (Object.keys(permissions).length) doc[AclSym] = permissions;
+    });
+    // }
 
     if (doc.proto instanceof Promise) {
         doc.proto.then(fetchProto);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 6d2d715bd..ee01f6213 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -129,28 +129,31 @@ export function setGroups(groups: string[]) {
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
     if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
 
-    const HierarchyMapping = new Map<symbol, number>([
-        [AclPrivate, 0],
-        [AclReadonly, 1],
-        [AclAddonly, 2],
-        [AclEdit, 3]
-    ]);
-
     if (!target[AclSym] && target instanceof Doc) {
         fetchProto(target);
     }
 
+
     if (target[AclSym] && Object.keys(target[AclSym]).length) {
 
-        if (target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
+        // console.log(target[AclSym]);
+
+        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
 
         if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
-        if (target[AclSym].ACL) return target[AclSym].ACL;
+        // if (target[AclSym].ACL) return target[AclSym].ACL;
 
         let effectiveAcl = AclPrivate;
         let aclPresent = false;
 
+        const HierarchyMapping = new Map<symbol, number>([
+            [AclPrivate, 0],
+            [AclReadonly, 1],
+            [AclAddonly, 2],
+            [AclEdit, 3]
+        ]);
+
         for (const [key, value] of Object.entries(target[AclSym])) {
             if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
                 if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
-- 
cgit v1.2.3-70-g09d2


From a22f9b3c98c11d1170d1b0548bab61fc73fbc44f Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 23 Jul 2020 22:20:49 +0530
Subject: groups bugfix

---
 src/client/util/GroupManager.tsx          | 8 ++++++++
 src/client/views/DocumentDecorations.scss | 5 +++--
 src/client/views/DocumentDecorations.tsx  | 6 +++---
 3 files changed, 14 insertions(+), 5 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 2e5ecc543..a679c9087 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -48,6 +48,14 @@ export default class GroupManager extends React.Component<{}> {
 
     componentDidMount() {
         this.populateUsers();
+        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));
+            });
+
+            setGroups(this.currentUserGroups);
+        });
     }
 
     /**
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 5948ada88..424a06431 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -157,6 +157,7 @@ $linkGap : 3px;
         grid-column-end: 2;
         pointer-events: all;
         padding-left: 5px;
+        cursor: pointer;
     }
 
     .documentDecorations-title {
@@ -204,7 +205,7 @@ $linkGap : 3px;
     width: 20px;
 }
 
-.documentDecorations-closeButton {
+.documentDecorations-openInTab {
     opacity: 1;
     grid-column-start: 4;
     grid-column-end: 5;
@@ -216,7 +217,7 @@ $linkGap : 3px;
     margin-top: auto;
 }
 
-.documentDecorations-minimizeButton {
+.documentDecorations-closeButton {
     opacity: 1;
     grid-column-start: 1;
     grid-column-end: 3;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fec4ad9e0..46411c05b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -549,8 +549,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="cog" />
                 </div></Tooltip>) : (
-                <Tooltip title={<><div className="dash-tooltip">Iconify</div></>} placement="top">
-                    <div className="documentDecorations-minimizeButton" onClick={this.onCloseClick}>
+                <Tooltip title={<><div className="dash-tooltip">Delete</div></>} placement="top">
+                    <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
                         {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
                         <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
                     </div></Tooltip>);
@@ -616,7 +616,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                             <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
                                 {"_"}
                             </div></Tooltip>}
-                    <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
+                    <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}>
                         {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
                     </div></Tooltip>
                     <div id="documentDecorations-rotation" className="documentDecorations-rotation"
-- 
cgit v1.2.3-70-g09d2


From 9d3308dd8fbcbbd854c66a3e53ca4ba66853dd30 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 23 Jul 2020 22:24:23 +0530
Subject: groups bugfix

---
 src/client/util/GroupManager.tsx | 28 ++++++++++++----------------
 1 file changed, 12 insertions(+), 16 deletions(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index a679c9087..7d02b1e66 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -48,14 +48,6 @@ export default class GroupManager extends React.Component<{}> {
 
     componentDidMount() {
         this.populateUsers();
-        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));
-            });
-
-            setGroups(this.currentUserGroups);
-        });
     }
 
     /**
@@ -82,6 +74,17 @@ export default class GroupManager extends React.Component<{}> {
         return Promise.all(evaluating);
     }
 
+    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));
+            });
+
+            setGroups(this.currentUserGroups);
+        });
+    }
+
     /**
      * @returns the options to be rendered in the dropdown menu to add users and create a group.
      */
@@ -97,14 +100,7 @@ export default class GroupManager extends React.Component<{}> {
         SelectionManager.DeselectAll();
         this.isOpen = true;
         this.populateUsers();
-        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));
-            });
-
-            setGroups(this.currentUserGroups);
-        });
+        this.populateGroups();
     }
 
     /**
-- 
cgit v1.2.3-70-g09d2


From 17ccb0a1a37c906c0151d3da5e92de3d94205924 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 24 Jul 2020 01:45:05 +0530
Subject: groups and acl bugfix

---
 src/client/util/GroupManager.tsx | 1 +
 src/fields/util.ts               | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 7d02b1e66..a727d21d0 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -48,6 +48,7 @@ export default class GroupManager extends React.Component<{}> {
 
     componentDidMount() {
         this.populateUsers();
+        this.populateGroups();
     }
 
     /**
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 20b072786..cd1e24826 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -210,7 +210,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
         return true;
     }
 
-    if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
+    if (typeof prop === "string" && prop.startsWith("ACL") && ((target.author && Doc.CurrentUserEmail !== target.author) || !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value))) return true;
+    // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
-- 
cgit v1.2.3-70-g09d2


From 2f49497e9d74eda97b2327ca4dbcb0a11ac6c6c9 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 24 Jul 2020 17:11:09 +0530
Subject: added admin acl + renamed LinkCreatedBox to TaskCompletedBox and
 added custom text functionality + popups on sharing docs and creating groups
 + relocated SharingPermissions

---
 src/client/util/GroupManager.tsx                | 15 +++++++-
 src/client/util/SharingManager.tsx              | 46 +++++++++++++++----------
 src/client/views/DocComponent.tsx               |  3 +-
 src/client/views/MainView.tsx                   |  4 +--
 src/client/views/collections/CollectionView.tsx |  8 ++---
 src/client/views/nodes/DocumentLinksButton.tsx  | 18 +++++-----
 src/client/views/nodes/DocumentView.tsx         | 17 +++++----
 src/client/views/nodes/LinkCreatedBox.scss      | 21 -----------
 src/client/views/nodes/LinkCreatedBox.tsx       | 31 -----------------
 src/client/views/nodes/LinkDescriptionPopup.tsx |  4 +--
 src/client/views/nodes/TaskCompletedBox.scss    | 20 +++++++++++
 src/client/views/nodes/TaskCompletedBox.tsx     | 32 +++++++++++++++++
 src/fields/Doc.ts                               |  7 ++--
 src/fields/util.ts                              | 28 +++++++++------
 14 files changed, 143 insertions(+), 111 deletions(-)
 delete mode 100644 src/client/views/nodes/LinkCreatedBox.scss
 delete mode 100644 src/client/views/nodes/LinkCreatedBox.tsx
 create mode 100644 src/client/views/nodes/TaskCompletedBox.scss
 create mode 100644 src/client/views/nodes/TaskCompletedBox.tsx

(limited to 'src/client/util/GroupManager.tsx')

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index a727d21d0..72fba5c1b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -16,6 +16,7 @@ import { StrCast, Cast } from "../../fields/Types";
 import GroupMemberView from "./GroupMemberView";
 import { setGroups } from "../../fields/util";
 import { DocServer } from "../DocServer";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
 
 library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle);
 
@@ -36,11 +37,13 @@ export default class GroupManager extends React.Component<{}> {
     @observable currentGroup: Opt<Doc>; // the currently selected group.
     @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();
     private currentUserGroups: string[] = [];
     @observable private buttonColour: "#979797" | "black" = "#979797";
     @observable private groupSort: "ascending" | "descending" | "none" = "none";
 
 
+
     constructor(props: Readonly<{}>) {
         super(props);
         GroupManager.Instance = this;
@@ -303,6 +306,14 @@ export default class GroupManager extends React.Component<{}> {
         this.selectedUsers = null;
         this.inputRef.current.value = "";
         this.buttonColour = "#979797";
+
+        const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect();
+        TaskCompletionBox.popupX = left - 2 * width;
+        TaskCompletionBox.popupY = top;
+        TaskCompletionBox.textDisplayed = "Group created!";
+        TaskCompletionBox.taskCompleted = true;
+        setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
     }
 
     private get groupCreationModal() {
@@ -345,7 +356,9 @@ export default class GroupManager extends React.Component<{}> {
                         })
                     }}
                 />
-                <button onClick={this.createGroup}
+                <button
+                    ref={this.createGroupButtonRef}
+                    onClick={this.createGroup}
                     style={{ background: this.buttonColour }}
                     disabled={this.buttonColour === "#979797"}
                 >
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 9c857a7c0..452a58d21 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -20,7 +20,8 @@ import GroupMemberView from "./GroupMemberView";
 import Select from "react-select";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { List } from "../../fields/List";
-import { distributeAcls } from "../../fields/util";
+import { distributeAcls, SharingPermissions } from "../../fields/util";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
 
 library.add(fa.faCopy, fa.faTimes);
 
@@ -29,13 +30,6 @@ export interface User {
     userDocumentId: string;
 }
 
-export enum SharingPermissions {
-    Edit = "Can Edit",
-    Add = "Can Add",
-    View = "Can View",
-    None = "Not Shared"
-}
-
 interface GroupOptions {
     label: string;
     options: UserOptions[];
@@ -69,6 +63,8 @@ export default class SharingManager extends React.Component<{}> {
     @observable private permissions: SharingPermissions = SharingPermissions.Edit;
     @observable private individualSort: "ascending" | "descending" | "none" = "none";
     @observable private groupSort: "ascending" | "descending" | "none" = "none";
+    private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
+
 
 
     // private get linkVisible() {
@@ -90,6 +86,8 @@ export default class SharingManager extends React.Component<{}> {
     public close = action(() => {
         this.isOpen = false;
         this.users = [];
+        this.selectedUsers = null;
+
         setTimeout(action(() => {
             // this.copied = false;
             DictationOverlay.Instance.hasActiveModal = false;
@@ -235,7 +233,7 @@ export default class SharingManager extends React.Component<{}> {
     private get sharingOptions() {
         return Object.values(SharingPermissions).map(permission => {
             return (
-                <option key={permission} value={permission}>
+                <option key={permission} value={permission} selected={permission === SharingPermissions.Edit}>
                     {permission}
                 </option>
             );
@@ -284,15 +282,25 @@ export default class SharingManager extends React.Component<{}> {
 
     @action
     share = () => {
-        this.selectedUsers?.forEach(user => {
-            if (user.value.includes(indType)) {
-                this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
-            }
-            else {
-                this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
-            }
-        });
-        this.selectedUsers = null;
+        if (this.selectedUsers) {
+            this.selectedUsers.forEach(user => {
+                if (user.value.includes(indType)) {
+                    this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
+                }
+                else {
+                    this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+                }
+            });
+
+            const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
+            TaskCompletionBox.popupX = left - 1.5 * width;
+            TaskCompletionBox.popupY = top - height;
+            TaskCompletionBox.textDisplayed = "Document shared!";
+            TaskCompletionBox.taskCompleted = true;
+            setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
+            this.selectedUsers = null;
+        }
     }
 
     sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
@@ -456,7 +464,7 @@ export default class SharingManager extends React.Component<{}> {
                             <select className="permissions-select" onChange={this.handlePermissionsChange}>
                                 {this.sharingOptions}
                             </select>
-                            <button className="share-button" onClick={this.share}>
+                            <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
                                 Share
                             </button>
                         </div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 95c1bcda8..00d570670 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -7,8 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util';
-import { SharingPermissions } from '../util/SharingManager';
+import { GetEffectiveAcl, getPlaygroundMode, SharingPermissions } from '../../fields/util';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 225fb2e8e..81f2feee0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -61,7 +61,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
@@ -623,7 +623,7 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
-            <LinkCreatedBox />
+            <TaskCompletionBox />
             <ContextMenu />
             <FormatShapePane />
             <RadialMenu />
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 53fd83f26..4848508be 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate, AclAdmin } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } from '../../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -48,7 +48,6 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import CollectionMenu from './CollectionMenu';
-import { SharingPermissions } from '../../util/SharingManager';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -111,7 +110,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         [AclPrivate, SharingPermissions.None],
         [AclReadonly, SharingPermissions.View],
         [AclAddonly, SharingPermissions.Add],
-        [AclEdit, SharingPermissions.Edit]
+        [AclEdit, SharingPermissions.Edit],
+        [AclAdmin, SharingPermissions.Admin]
     ]);
 
     get collectionViewType(): CollectionViewType | undefined {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 13bec5d7f..f0e80045d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,7 +10,7 @@ import React = require("react");
 import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
 import { LinkManager } from "../../util/LinkManager";
 import { Tooltip } from "@material-ui/core";
@@ -101,15 +101,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
                         runInAction(() => {
                             if (linkDoc) {
-                                LinkCreatedBox.popupX = e.screenX;
-                                LinkCreatedBox.popupY = e.screenY - 133;
-                                LinkCreatedBox.linkCreated = true;
+                                TaskCompletionBox.popupX = e.screenX;
+                                TaskCompletionBox.popupY = e.screenY - 133;
+                                TaskCompletionBox.taskCompleted = true;
 
                                 LinkDescriptionPopup.popupX = e.screenX;
                                 LinkDescriptionPopup.popupY = e.screenY - 100;
                                 LinkDescriptionPopup.descriptionPopup = true;
 
-                                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                                setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
                             }
 
                         });
@@ -134,9 +134,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
                     runInAction(() => {
                         if (linkDoc) {
-                            LinkCreatedBox.popupX = e.screenX;
-                            LinkCreatedBox.popupY = e.screenY - 133;
-                            LinkCreatedBox.linkCreated = true;
+                            TaskCompletionBox.popupX = e.screenX;
+                            TaskCompletionBox.popupY = e.screenY - 133;
+                            TaskCompletionBox.taskCompleted = true;
 
                             if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
                                 LinkDescriptionPopup.popupX = e.screenX;
@@ -144,7 +144,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                                 LinkDescriptionPopup.descriptionPopup = true;
                             }
 
-                            setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                            setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
                         }
                     });
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e492650b..1d4b61545 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx, GetEffectiveAcl } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -25,7 +25,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { Scripting } from '../../util/Scripting';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager, { SharingPermissions } from '../../util/SharingManager';
+import SharingManager from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -41,7 +41,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { LinkCreatedBox } from './LinkCreatedBox';
+import { TaskCompletionBox } from './TaskCompletedBox';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { LinkManager } from '../../util/LinkManager';
 
@@ -629,15 +629,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const makeLink = action((linkDoc: Doc) => {
             LinkManager.currentLink = linkDoc;
 
-            LinkCreatedBox.popupX = de.x;
-            LinkCreatedBox.popupY = de.y - 33;
-            LinkCreatedBox.linkCreated = true;
+            TaskCompletionBox.popupX = de.x;
+            TaskCompletionBox.popupY = de.y - 33;
+            TaskCompletionBox.taskCompleted = true;
 
             LinkDescriptionPopup.popupX = de.x;
             LinkDescriptionPopup.popupY = de.y;
             LinkDescriptionPopup.descriptionPopup = true;
 
-            setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500);
+            setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
         });
         if (de.complete.annoDragData) {
             /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner
@@ -818,6 +818,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
+        // to be removed for baseline
+        cm.addItem({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+
         // const existingAcls = cm.findByDescription("Privacy...");
         // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/LinkCreatedBox.scss
deleted file mode 100644
index 3cbd38b55..000000000
--- a/src/client/views/nodes/LinkCreatedBox.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.linkCreatedBox-fade {
-    border: 1px solid rgb(100, 100, 100);
-
-
-    width: auto;
-    position: absolute;
-
-    height: auto;
-    z-index: 10000;
-    border-radius: 13px;
-    font-size: 13px;
-    white-space: nowrap;
-
-    color: rgb(100, 100, 100);
-    background-color: rgba(250, 250, 250, 0.85);
-    padding-top: 6.5px;
-    padding-bottom: 6.5px;
-    font-weight: bold;
-    padding-left: 9px;
-    padding-right: 9px;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
deleted file mode 100644
index 648ae23c8..000000000
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
-import "./LinkCreatedBox.scss";
-import { observable, action } from "mobx";
-import { Fade } from "@material-ui/core";
-
-
-@observer
-export class LinkCreatedBox extends React.Component<{}> {
-
-    @observable public static linkCreated: boolean = false;
-    @observable public static popupX: number = 500;
-    @observable public static popupY: number = 150;
-
-    @action
-    public static changeLinkCreated = () => {
-        LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated;
-    }
-
-    render() {
-        return <Fade in={LinkCreatedBox.linkCreated}>
-            <div className="linkCreatedBox-fade"
-                style={{
-                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
-                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
-                }}>Link Created</div>
-        </Fade>;
-    }
-} 
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 06e8d30d1..d8fe47f4e 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -4,7 +4,7 @@ import "./LinkDescriptionPopup.scss";
 import { observable, action } from "mobx";
 import { EditableView } from "../EditableView";
 import { LinkManager } from "../../util/LinkManager";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
 
 
 @observer
@@ -31,7 +31,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     onClick = (e: PointerEvent) => {
         if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
             LinkDescriptionPopup.descriptionPopup = false;
-            LinkCreatedBox.linkCreated = false;
+            TaskCompletionBox.taskCompleted = false;
         }
     }
 
diff --git a/src/client/views/nodes/TaskCompletedBox.scss b/src/client/views/nodes/TaskCompletedBox.scss
new file mode 100644
index 000000000..80b750b39
--- /dev/null
+++ b/src/client/views/nodes/TaskCompletedBox.scss
@@ -0,0 +1,20 @@
+.taskCompletedBox-fade {
+    border: 1px solid rgb(100, 100, 100);
+
+    width: auto;
+    position: absolute;
+
+    height: auto;
+    z-index: 10000;
+    border-radius: 13px;
+    font-size: 13px;
+    white-space: nowrap;
+
+    color: rgb(100, 100, 100);
+    background-color: rgba(250, 250, 250, 0.85);
+    padding-top: 6.5px;
+    padding-bottom: 6.5px;
+    font-weight: bold;
+    padding-left: 9px;
+    padding-right: 9px;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
new file mode 100644
index 000000000..1ba2d1713
--- /dev/null
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -0,0 +1,32 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import "./TaskCompletedBox.scss";
+import { observable, action } from "mobx";
+import { Fade } from "@material-ui/core";
+
+
+@observer
+export class TaskCompletionBox extends React.Component<{}> {
+
+    @observable public static taskCompleted: boolean = false;
+    @observable public static popupX: number = 500;
+    @observable public static popupY: number = 150;
+    @observable public static textDisplayed: string = "Link Created";
+
+    @action
+    public static toggleTaskCompleted = () => {
+        TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted;
+    }
+
+    render() {
+        return <Fade in={TaskCompletionBox.taskCompleted}>
+            <div className="taskCompletedBox-fade"
+                style={{
+                    left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500,
+                    top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150,
+                }}>{TaskCompletionBox.textDisplayed}</div>
+        </Fade>;
+    }
+} 
\ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 917a6853c..0c816461c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -19,9 +19,8 @@ import { DateField } from "./DateField";
 import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions } from "./util";
 import { LinkManager } from "../client/util/LinkManager";
-import { SharingPermissions } from "../client/util/SharingManager";
 import JSZip = require("jszip");
 import { saveAs } from "file-saver";
 
@@ -103,6 +102,7 @@ export const AclPrivate = Symbol("AclOwnerOnly");
 export const AclReadonly = Symbol("AclReadOnly");
 export const AclAddonly = Symbol("AclAddonly");
 export const AclEdit = Symbol("AclEdit");
+export const AclAdmin = Symbol("AclAdmin");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
 const CachedUpdates = Symbol("Cached updates");
 
@@ -110,7 +110,8 @@ const AclMap = new Map<string, symbol>([
     [SharingPermissions.None, AclPrivate],
     [SharingPermissions.View, AclReadonly],
     [SharingPermissions.Add, AclAddonly],
-    [SharingPermissions.Edit, AclEdit]
+    [SharingPermissions.Edit, AclEdit],
+    [SharingPermissions.Admin, AclAdmin]
 ]);
 
 export function fetchProto(doc: Doc) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index cd1e24826..eba398dfb 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast, AclAdmin } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -9,7 +9,6 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast, StrCast } from "./Types";
-import { SharingPermissions } from "../client/util/SharingManager";
 
 
 function _readOnlySetter(): never {
@@ -127,12 +126,20 @@ export function setGroups(groups: string[]) {
     currentUserGroups = groups;
 }
 
+export enum SharingPermissions {
+    Admin = "Admin",
+    Edit = "Can Edit",
+    Add = "Can Add",
+    View = "Can View",
+    None = "Not Shared"
+}
+
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
     if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
 
     if (target[AclSym] && Object.keys(target[AclSym]).length) {
 
-        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
+        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;
 
         if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
@@ -143,7 +150,8 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
             [AclPrivate, 0],
             [AclReadonly, 1],
             [AclAddonly, 2],
-            [AclEdit, 3]
+            [AclEdit, 3],
+            [AclAdmin, 4]
         ]);
 
         for (const [key, value] of Object.entries(target[AclSym])) {
@@ -157,7 +165,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
         }
         return aclPresent ? effectiveAcl : AclEdit;
     }
-    return AclEdit;
+    return AclAdmin;
 }
 
 export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
@@ -166,7 +174,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
         ["Not Shared", 0],
         ["Can View", 1],
         ["Can Add", 2],
-        ["Can Edit", 3]
+        ["Can Edit", 3],
+        ["Admin", 4]
     ]);
 
     const dataDoc = target[DataSym];
@@ -206,11 +215,10 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
-    if (GetEffectiveAcl(target, in_prop) !== AclEdit) {
-        return true;
-    }
+    const effectiveAcl = GetEffectiveAcl(target, in_prop);
+    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
 
-    if (typeof prop === "string" && prop.startsWith("ACL") && ((target.author && Doc.CurrentUserEmail !== target.author) || !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value))) return true;
+    if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
     // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
-- 
cgit v1.2.3-70-g09d2

