diff options
Diffstat (limited to 'src/client/util/GroupManager.tsx')
-rw-r--r-- | src/client/util/GroupManager.tsx | 253 |
1 files changed, 124 insertions, 129 deletions
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 62d656c5d..59334f6a2 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,19 +1,19 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import Select from 'react-select'; -import * as RequestPromise from "request-promise"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; -import { StrCast, Cast } from "../../fields/Types"; -import { Utils } from "../../Utils"; -import { MainViewModal } from "../views/MainViewModal"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import "./GroupManager.scss"; -import { GroupMemberView } from "./GroupMemberView"; -import { SharingManager, User } from "./SharingManager"; -import { listSpec } from "../../fields/Schema"; -import { DateField } from "../../fields/DateField"; +import * as RequestPromise from 'request-promise'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; +import { StrCast, Cast } from '../../fields/Types'; +import { Utils } from '../../Utils'; +import { MainViewModal } from '../views/MainViewModal'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import './GroupManager.scss'; +import { GroupMemberView } from './GroupMemberView'; +import { SharingManager, User } from './SharingManager'; +import { listSpec } from '../../fields/Schema'; +import { DateField } from '../../fields/DateField'; /** * Interface for options for the react-select component @@ -25,7 +25,6 @@ export interface UserOptions { @observer export class GroupManager extends React.Component<{}> { - static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @observable private users: string[] = []; // list of users populated from the database. @@ -34,24 +33,26 @@ export class GroupManager extends React.Component<{}> { @observable private createGroupModalOpen: boolean = false; private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box. private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button - @observable private buttonColour: "#979797" | "black" = "#979797"; - @observable private groupSort: "ascending" | "descending" | "none" = "none"; + @observable private buttonColour: '#979797' | 'black' = '#979797'; + @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; constructor(props: Readonly<{}>) { super(props); GroupManager.Instance = this; } - componentDidMount() { this.populateUsers(); } + componentDidMount() { + this.populateUsers(); + } /** * Fetches the list of users stored on the database. */ populateUsers = async () => { - const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); - } + }; /** * @returns the options to be rendered in the dropdown menu to add users and create a group. @@ -68,11 +69,11 @@ export class GroupManager extends React.Component<{}> { // SelectionManager.DeselectAll(); this.isOpen = true; this.populateUsers(); - } + }; /** * Hides the GroupManager. - */ + */ @action close = () => { this.isOpen = false; @@ -81,26 +82,32 @@ export class GroupManager extends React.Component<{}> { // this.users = []; this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; - } + }; /** * @returns the database of groups. */ - @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; } + @computed get GroupManagerDoc(): Doc | undefined { + return Doc.UserDoc().globalGroupDatabase as Doc; + } /** * @returns a list of all group documents. */ - @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); } + @computed get allGroups(): Doc[] { + return DocListCast(this.GroupManagerDoc?.data); + } /** * @returns the members of the admin group. */ - @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; } + @computed get adminGroupMembers(): string[] { + return this.getGroup('Admin') ? JSON.parse(StrCast(this.getGroup('Admin')!.members)) : ''; + } /** * @returns a group document based on the group name. - * @param groupName + * @param groupName */ getGroup(groupName: string): Doc | undefined { return this.allGroups.find(group => group.title === groupName); @@ -114,10 +121,9 @@ export class GroupManager extends React.Component<{}> { return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[]; } - /** * @returns a boolean indicating whether the current user has access to edit group documents. - * @param groupDoc + * @param groupDoc */ hasEditAccess(groupDoc: Doc): boolean { if (!groupDoc) return false; @@ -127,12 +133,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that sets up the group document. - * @param groupName - * @param memberEmails + * @param groupName + * @param memberEmails */ createGroupDoc(groupName: string, memberEmails: string[] = []) { - const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName; - const groupDoc = new Doc("GROUP:" + name, true); + const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName; + const groupDoc = new Doc('GROUP:' + name, true); groupDoc.title = name; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); @@ -141,12 +147,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that adds a group document to the database of group documents and @returns whether it was successfully added or not. - * @param groupDoc + * @param groupDoc */ addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { - Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc); - this.GroupManagerDoc["data-lastModified"] = new DateField; + Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc); + this.GroupManagerDoc['data-lastModified'] = new DateField(); return true; } return false; @@ -154,20 +160,20 @@ export class GroupManager extends React.Component<{}> { /** * Deletes a group from the database of group documents and @returns whether the group was deleted or not. - * @param group + * @param group */ @action deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { - Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); + Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); if (members.includes(Doc.CurrentUserEmail)) { const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group); index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1); } - this.GroupManagerDoc["data-lastModified"] = new DateField; + this.GroupManagerDoc['data-lastModified'] = new DateField(); if (group === this.currentGroup) { this.currentGroup = undefined; } @@ -179,8 +185,8 @@ export class GroupManager extends React.Component<{}> { /** * Adds a member to a group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ addMemberToGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -188,14 +194,14 @@ export class GroupManager extends React.Component<{}> { !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.shareWithAddedMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } /** * Removes a member from the group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ removeMemberFromGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -205,27 +211,27 @@ export class GroupManager extends React.Component<{}> { const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.removeMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } } /** * Handles changes in the users selected in the "Select users" dropdown. - * @param selectedOptions + * @param selectedOptions */ @action handleChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; - } + }; /** * Creates the group when the enter key has been pressed (when in the input). - * @param e + * @param e */ handleKeyDown = (e: React.KeyboardEvent) => { - e.key === "Enter" && this.createGroup(); - } + e.key === 'Enter' && this.createGroup(); + }; /** * Handles the input of required fields in the setup of a group and resets the relevant variables. @@ -234,32 +240,37 @@ export class GroupManager extends React.Component<{}> { createGroup = () => { const { value } = this.inputRef.current!; if (!value) { - alert("Please enter a group name"); + alert('Please enter a group name'); return; } - if (["admin", "public", "override"].includes(value.toLowerCase())) { - if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) { + if (['admin', 'public', 'override'].includes(value.toLowerCase())) { + if (value.toLowerCase() !== 'admin' || (value.toLowerCase() === 'admin' && this.getGroup('Admin'))) { alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`); return; } } if (this.getGroup(value)) { - alert("Please select a unique group name"); + alert('Please select a unique group name'); return; } - this.createGroupDoc(value, this.selectedUsers?.map(user => user.value)); + this.createGroupDoc( + value, + this.selectedUsers?.map(user => user.value) + ); this.selectedUsers = null; - this.inputRef.current!.value = ""; - this.buttonColour = "#979797"; + 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.textDisplayed = 'Group created!'; TaskCompletionBox.taskCompleted = true; - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); - - } + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); + }; /** * @returns the MainViewModal which allows the user to create groups. @@ -268,50 +279,43 @@ export class GroupManager extends React.Component<{}> { 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; TaskCompletionBox.taskCompleted = false; - })}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <p> + <b>New Group</b> + </p> + <div + className={'close-button'} + onClick={action(() => { + this.createGroupModalOpen = false; + TaskCompletionBox.taskCompleted = false; + })}> + <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> </div> </div> - <input - className="group-input" - ref={this.inputRef} - onKeyDown={this.handleKeyDown} - autoFocus - type="text" - placeholder="Group name" - onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} /> + <input className="group-input" ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} /> <Select isMulti options={this.options} onChange={this.handleChange} - placeholder={"Select users"} + 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 + transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined, }), - multiValue: (base) => ({ + multiValue: base => ({ ...base, - maxWidth: "50%", + maxWidth: '50%', '&:hover': { - maxWidth: "unset" - } - }) + maxWidth: 'unset', + }, + }), }} /> - <button - ref={this.createGroupButtonRef} - onClick={this.createGroup} - style={{ background: this.buttonColour }} - disabled={this.buttonColour === "#979797"} - > + <button ref={this.createGroupButtonRef} onClick={this.createGroup} style={{ background: this.buttonColour }} disabled={this.buttonColour === '#979797'}> Create </button> </div> @@ -322,8 +326,12 @@ export class GroupManager extends React.Component<{}> { isDisplayed={this.createGroupModalOpen} interactive={true} contents={contents} - dialogueBoxStyle={{ width: "90%", height: "70%" }} - closeOnExternalClick={action(() => { this.createGroupModalOpen = false; this.selectedUsers = null; TaskCompletionBox.taskCompleted = false; })} + dialogueBoxStyle={{ width: '90%', height: '70%' }} + closeOnExternalClick={action(() => { + this.createGroupModalOpen = false; + this.selectedUsers = null; + TaskCompletionBox.taskCompleted = false; + })} /> ); } @@ -332,7 +340,6 @@ export 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.title); const g2 = StrCast(d2.title); @@ -340,62 +347,50 @@ export class GroupManager extends React.Component<{}> { return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; }; - const groups = this.groupSort === "ascending" ? this.allGroups.sort(sortGroups) : this.groupSort === "descending" ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; + const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; return ( <div className="group-interface"> {this.groupCreationModal} - {this.currentGroup ? - <GroupMemberView - group={this.currentGroup} - onCloseButtonClick={action(() => this.currentGroup = undefined)} - /> - : null} + {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null} <div className="group-heading"> - <p><b>Manage Groups</b></p> - <button onClick={action(() => this.createGroupModalOpen = true)}> - <FontAwesomeIcon icon={"plus"} size={"sm"} /> Create Group + <p> + <b>Manage Groups</b> + </p> + <button onClick={action(() => (this.createGroupModalOpen = true))}> + <FontAwesomeIcon icon={'plus'} size={'sm'} /> Create Group </button> - <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <div className={'close-button'} onClick={this.close}> + <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> </div> </div> <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" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> - : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> - : <FontAwesomeIcon icon={"caret-right"} size={"xs"} /> - } + <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> + Name{' '} + {this.groupSort === 'ascending' ? ( + <FontAwesomeIcon icon={'caret-up'} size={'xs'} /> + ) : this.groupSort === 'descending' ? ( + <FontAwesomeIcon icon={'caret-down'} size={'xs'} /> + ) : ( + <FontAwesomeIcon icon={'caret-right'} size={'xs'} /> + )} </div> <div className="group-body"> - {groups.map(group => - <div - className="group-row" - key={StrCast(group.title || group.groupName)} - > - <div className="group-name" >{group.title || group.groupName}</div> - <div className="group-info" onClick={action(() => this.currentGroup = group)}> - <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> + {groups.map(group => ( + <div className="group-row" key={StrCast(group.title || group.groupName)}> + <div className="group-name">{StrCast(group.title || group.groupName)}</div> + <div className="group-info" onClick={action(() => (this.currentGroup = group))}> + <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> </div> </div> - )} + ))} </div> </div> - </div> ); } render() { - return <MainViewModal - contents={this.groupInterface} - isDisplayed={this.isOpen} - interactive={true} - dialogueBoxStyle={{ zIndex: 1002 }} - overlayStyle={{ zIndex: 1001 }} - closeOnExternalClick={this.close} - />; + return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />; } -}
\ No newline at end of file +} |