diff options
| author | Melissa Zhang <mzhang19096@gmail.com> | 2020-08-03 15:15:33 -0700 | 
|---|---|---|
| committer | Melissa Zhang <mzhang19096@gmail.com> | 2020-08-03 15:15:33 -0700 | 
| commit | e9737412cb3d60ed76c31c9571e9516518cddd77 (patch) | |
| tree | 407f4a4cbc4947d4057d317dc3a8b7369197c01d /src/client/util/SharingManager.tsx | |
| parent | 1713f5415294b996e6dbbe9ca45f0f4511c69824 (diff) | |
| parent | ef5dbc6ad8bdb70f3e68d2fc4ace1a483b031add (diff) | |
merge with master
Diffstat (limited to 'src/client/util/SharingManager.tsx')
| -rw-r--r-- | src/client/util/SharingManager.tsx | 226 | 
1 files changed, 151 insertions, 75 deletions
| diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 0d8b33fbe..9d91ce1ba 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, DocListCastAsync } from "../../fields/Doc"; +import { Doc, Opt, DocListCastAsync, AclAdmin, DataSym, AclPrivate } from "../../fields/Doc";  import { DocServer } from "../DocServer";  import { Cast, StrCast } from "../../fields/Types";  import * as RequestPromise from "request-promise"; @@ -19,7 +19,7 @@ import GroupMemberView from "./GroupMemberView";  import Select from "react-select";  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";  import { List } from "../../fields/List"; -import { distributeAcls, SharingPermissions } from "../../fields/util"; +import { distributeAcls, SharingPermissions, GetEffectiveAcl } from "../../fields/util";  import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";  export interface User { @@ -27,7 +27,10 @@ export interface User {      userDocumentId: string;  } -interface GroupOptions { +/** + * Interface for grouped options for the react-select component. + */ +interface GroupedOptions {      label: string;      options: UserOptions[];  } @@ -36,9 +39,13 @@ interface GroupOptions {  // const PublicKey = "publicLinkPermissions";  // const DefaultColor = "black"; -const groupType = "!groupType/"; +// used to differentiate between individuals and groups when sharing  const indType = "!indType/"; +const groupType = "!groupType/"; +/** + * A user who also has a notificationDoc. + */  interface ValidatedUser {      user: User;      notificationDoc: Doc; @@ -49,41 +56,43 @@ const storage = "data";  @observer  export default class SharingManager extends React.Component<{}> {      public static Instance: SharingManager; -    @observable private isOpen = false; -    @observable private users: ValidatedUser[] = []; -    @observable private targetDoc: Doc | undefined; -    @observable private targetDocView: DocumentView | undefined; +    @observable private isOpen = false; // whether the SharingManager modal is open or not +    @observable private users: ValidatedUser[] = []; // the list of users with notificationDocs +    @observable private targetDoc: Doc | undefined; // the document being shared +    @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared      // @observable private copied = false; -    @observable private dialogueBoxOpacity = 1; -    @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 shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); - - +    @observable private dialogueBoxOpacity = 1; // for the modal +    @observable private overlayOpacity = 0.4; // for the modal +    @observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with +    @observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users +    @observable private individualSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of individuals +    @observable private groupSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of groups +    private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup +    // if both showUserOptions and showGroupOptions are false then both are displayed +    @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component) +    @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)      // private get linkVisible() {      //     return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;      // }      public open = (target: DocumentView) => { -        SelectionManager.DeselectAll(); -        this.populateUsers().then(action(() => { +        runInAction(() => this.users = []); +        // SelectionManager.DeselectAll(); +        this.populateUsers(); +        runInAction(() => {              this.targetDocView = target;              this.targetDoc = target.props.Document;              DictationOverlay.Instance.hasActiveModal = true;              this.isOpen = true;              this.permissions = SharingPermissions.Edit; -        })); +        });      }      public close = action(() => {          this.isOpen = false; -        this.users = []; -        this.selectedUsers = null; +        this.selectedUsers = null; // resets the list of users and seleected users (in the react-select component)          setTimeout(action(() => {              // this.copied = false; @@ -97,7 +106,18 @@ export default class SharingManager extends React.Component<{}> {          SharingManager.Instance = this;      } +    /** +     * Populates the list of users. +     */ +    componentDidMount() { +        this.populateUsers(); +    } + +    /** +     * Populates the list of validated users (this.users) by adding registered users which have a sidebar-sharing. +     */      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 => { @@ -105,7 +125,7 @@ export default class SharingManager extends React.Component<{}> {              if (isCandidate) {                  const userDocument = await DocServer.GetRefField(user.userDocumentId);                  if (userDocument instanceof Doc) { -                    const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc); +                    const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc);                      runInAction(() => {                          if (notificationDoc instanceof Doc) {                              this.users.push({ user, notificationDoc }); @@ -117,58 +137,80 @@ export default class SharingManager extends React.Component<{}> {          return Promise.all(evaluating);      } -    setInternalGroupSharing = (group: Doc, permission: string) => { +    /** +     * Sets the permission on the target for the group. +     * @param group  +     * @param permission  +     */ +    setInternalGroupSharing = (group: Doc, permission: string, targetDoc?: Doc) => {          const members: string[] = JSON.parse(StrCast(group.members));          const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); -        const target = this.targetDoc!; +        const target = targetDoc || 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; -        distributeAcls(ACL, permission as SharingPermissions, this.targetDoc!); +        target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target); +        // if documents have been shared, add the target to that list if it doesn't already exist, otherwise create a new list with the target          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]);          users.forEach(({ notificationDoc }) => {              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); +                if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); // add the target to the notificationDoc if it hasn't already been added +                else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); // remove the target from the list if it already exists              });          });      } +    /** +     * Shares the documents shared with a group with a new user who has been added to that group. +     * @param group  +     * @param emailId  +     */      shareWithAddedMember = (group: Doc, emailId: string) => {          const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;          if (group.docsShared) {              DocListCastAsync(group.docsShared).then(docsShared => {                  docsShared?.forEach(doc => { -                    DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); +                    DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); // add the doc if it isn't already in the list                  });              });          }      } +    shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, target: Doc) => { +        const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); +        if (user) this.setInternalSharing(user, permission, target); +        else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, target); +    } + +    /** +     * Removes the documents shared with a user through a group when the user is removed from the group. +     * @param group  +     * @param emailId  +     */      removeMember = (group: Doc, emailId: string) => {          const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;          if (group.docsShared) {              DocListCastAsync(group.docsShared).then(docsShared => {                  docsShared?.forEach(doc => { -                    DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); +                    DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); // remove the doc only if it is in the list                  });              });          }      } +    /** +     * Removes a group's permissions from documents that have been shared with it. +     * @param group  +     */      removeGroup = (group: Doc) => {          if (group.docsShared) {              DocListCastAsync(group.docsShared).then(resolved => {                  resolved?.forEach(doc => {                      const ACL = `ACL-${StrCast(group.groupName)}`; -                    // doc[ACL] = doc[DataSym][ACL] = "Not Shared";                      distributeAcls(ACL, SharingPermissions.None, doc); @@ -182,14 +224,13 @@ export default class SharingManager extends React.Component<{}> {          }      } -    // @action -    setInternalSharing = (recipient: ValidatedUser, permission: string) => { +    setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {          const { user, notificationDoc } = recipient; -        const target = this.targetDoc!; +        const target = targetDoc || this.targetDoc!;          const key = user.email.replace('.', '_');          const ACL = `ACL-${key}`; -        distributeAcls(ACL, permission as SharingPermissions, this.targetDoc!); +        target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target);          if (permission !== SharingPermissions.None) {              DocListCastAsync(notificationDoc[storage]).then(resolved => { @@ -291,7 +332,7 @@ export default class SharingManager extends React.Component<{}> {              const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();              TaskCompletionBox.popupX = left - 1.5 * width; -            TaskCompletionBox.popupY = top - height; +            TaskCompletionBox.popupY = top - 1.5 * height;              TaskCompletionBox.textDisplayed = "Document shared!";              TaskCompletionBox.taskCompleted = true;              setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); @@ -315,45 +356,67 @@ export default class SharingManager extends React.Component<{}> {      private get sharingInterface() {          const groupList = GroupManager.Instance?.getAllGroups() || []; -        const sortedUsers = this.users.sort(this.sortUsers) +        const sortedUsers = this.users.slice().sort(this.sortUsers)              .map(({ user: { email } }) => ({ label: email, value: indType + email })); -        const sortedGroups = groupList.sort(this.sortGroups) +        const sortedGroups = groupList.slice().sort(this.sortGroups)              .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) })); -        const options: GroupOptions[] = GroupManager.Instance ? -            [ -                { +        const options: GroupedOptions[] = []; + +        if (GroupManager.Instance) { +            if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) { +                options.push({                      label: 'Individuals',                      options: sortedUsers                  }, -                { +                    { +                        label: 'Groups', +                        options: sortedGroups +                    }); +            } +            else if (this.showUserOptions) { +                options.push({ +                    label: 'Individuals', +                    options: sortedUsers +                }); +            } +            else { +                options.push({                      label: 'Groups',                      options: sortedGroups -                } -            ] -            : []; +                }); +            } +        }          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 effectiveAcl = this.targetDoc ? GetEffectiveAcl(this.targetDoc) : AclPrivate; +          const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => {              const userKey = user.email.replace('.', '_'); -            const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None); +            const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`]); -            return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : ( +            return !permissions || user.email === this.targetDoc?.author ? null : (                  <div                      key={userKey}                      className={"container"}                  >                      <span className={"padding"}>{user.email}</span>                      <div className="edit-actions"> -                        <select -                            className={"permissions-dropdown"} -                            value={permissions} -                            onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)} -                        > -                            {this.sharingOptions} -                        </select> +                        {effectiveAcl === AclAdmin ? ( +                            <select +                                className={"permissions-dropdown"} +                                value={permissions} +                                onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)} +                            > +                                {this.sharingOptions} +                            </select> +                        ) : ( +                                <div className={"permissions-dropdown"}> +                                    {permissions} +                                </div> +                            )}                      </div>                  </div>              ); @@ -365,20 +428,34 @@ export default class SharingManager extends React.Component<{}> {                      key={"owner"}                      className={"container"}                  > -                    <span className={"padding"}>{this.targetDoc?.author}</span> +                    <span className={"padding"}>{this.targetDoc?.author === Doc.CurrentUserEmail ? "Me" : this.targetDoc?.author}</span>                      <div className="edit-actions">                          <div className={"permissions-dropdown"}>                              Owner                          </div>                      </div>                  </div> -            ) +            ), +            this.targetDoc?.author !== Doc.CurrentUserEmail ? +                ( +                    <div +                        key={"me"} +                        className={"container"} +                    > +                        <span className={"padding"}>Me</span> +                        <div className="edit-actions"> +                            <div className={"permissions-dropdown"}> +                                {this.targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]} +                            </div> +                        </div> +                    </div> +                ) : null          );          const groupListContents = groups.map(group => { -            const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None); +            const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`]); -            return permissions === SharingPermissions.None ? null : ( +            return !permissions ? null : (                  <div                      key={StrCast(group.groupName)}                      className={"container"} @@ -400,7 +477,6 @@ export default class SharingManager extends React.Component<{}> {              );          }); -        const displayUserList = !userListContents?.every(user => user === null);          const displayGroupList = !groupListContents?.every(group => group === null);          return ( @@ -446,8 +522,7 @@ export default class SharingManager extends React.Component<{}> {                      <div className={"close-button"} onClick={this.close}>                          <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />                      </div> -                    {this.targetDoc?.author !== Doc.CurrentUserEmail ? null -                        : +                    {<div className="share-container">                          <div className="share-setup">                              <Select                                  className={"user-search"} @@ -457,6 +532,11 @@ export default class SharingManager extends React.Component<{}> {                                  options={options}                                  onChange={this.handleUsersChange}                                  value={this.selectedUsers} +                                styles={{ +                                    indicatorSeparator: () => ({ +                                        visibility: "hidden" +                                    }) +                                }}                              />                              <select className="permissions-select" onChange={this.handlePermissionsChange}>                                  {this.sharingOptions} @@ -465,6 +545,11 @@ export default class SharingManager extends React.Component<{}> {                                  Share                              </button>                          </div> +                        <div className="sort-checkboxes"> +                            <input type="checkbox" onChange={action(() => this.showUserOptions = !this.showUserOptions)} /> <label style={{ marginRight: 10 }}>Individuals</label> +                            <input type="checkbox" onChange={action(() => this.showGroupOptions = !this.showGroupOptions)} /> <label>Groups</label> +                        </div> +                    </div>                      }                      <div className="main-container">                          <div className={"individual-container"}> @@ -473,17 +558,8 @@ export default class SharingManager extends React.Component<{}> {                                  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 ? -                                        <div -                                            className={"none"} -                                        > -                                            There are no users this document has been shared with. -                                        </div> -                                        : -                                        userListContents -                                } +                            <div className={"users-list"} style={{ display: "block" }}>{/*200*/} +                                {userListContents}                              </div>                          </div>                          <div className={"group-container"}> | 
