diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/CaptureManager.tsx | 157 | ||||
| -rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
| -rw-r--r-- | src/client/util/GroupManager.tsx | 253 | ||||
| -rw-r--r-- | src/client/util/ScrollBox.tsx | 23 | ||||
| -rw-r--r-- | src/client/util/SharingManager.tsx | 566 |
5 files changed, 486 insertions, 515 deletions
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index c247afa26..0b5957fac 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -1,17 +1,17 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { convertToObject } from "typescript"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { BoolCast, StrCast, Cast } from "../../fields/Types"; -import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; -import { LightboxView } from "../views/LightboxView"; -import { MainViewModal } from "../views/MainViewModal"; -import "./CaptureManager.scss"; -import { SelectionManager } from "./SelectionManager"; -import { undoBatch } from "./UndoManager"; -const higflyout = require("@hig/flyout"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { convertToObject } from 'typescript'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { BoolCast, StrCast, Cast } from '../../fields/Types'; +import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; +import { LightboxView } from '../views/LightboxView'; +import { MainViewModal } from '../views/MainViewModal'; +import './CaptureManager.scss'; +import { SelectionManager } from './SelectionManager'; +import { undoBatch } from './UndoManager'; +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,32 +22,31 @@ export class CaptureManager extends React.Component<{}> { @observable _document: any; @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. - constructor(props: {}) { super(props); CaptureManager.Instance = this; } - public close = action(() => this.isOpen = false); + public close = action(() => (this.isOpen = false)); public open = action((doc: Doc) => { this.isOpen = true; this._document = doc; }); - @computed get visibilityContent() { - - return <div className="capture-block"> - <div className="capture-block-title">Visibility</div> - <div className="capture-block-radio"> - <div className="radio-container"> - <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private - </div> - <div className="radio-container"> - <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public + return ( + <div className="capture-block"> + <div className="capture-block-title">Visibility</div> + <div className="capture-block-radio"> + <div className="radio-container"> + <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private + </div> + <div className="radio-container"> + <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public + </div> </div> </div> - </div>; + ); } @computed get linksContent() { @@ -66,75 +65,79 @@ export class CaptureManager extends React.Component<{}> { order.push( <div className="list-item"> <div className="number">{i}</div> - {(l.anchor1 as Doc).title} + {StrCast((l.anchor1 as Doc).title)} </div> ); } }); } - return <div className="capture-block"> - <div className="capture-block-title">Links</div> - <div className="capture-block-list"> - {order} + return ( + <div className="capture-block"> + <div className="capture-block-title">Links</div> + <div className="capture-block-list">{order}</div> </div> - </div>; + ); } @computed get closeButtons() { - return <div className="capture-block"> - <div className="buttons"> - <div className="save" onClick={() => { - LightboxView.SetLightboxDoc(this._document); - this.close(); - }}> - Save - </div> - <div className="cancel" onClick={() => { - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); - this.close(); - }}> - Cancel + return ( + <div className="capture-block"> + <div className="buttons"> + <div + className="save" + onClick={() => { + LightboxView.SetLightboxDoc(this._document); + this.close(); + }}> + Save + </div> + <div + className="cancel" + onClick={() => { + const selected = SelectionManager.Views().slice(); + SelectionManager.DeselectAll(); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + this.close(); + }}> + Cancel + </div> </div> </div> - </div>; + ); } - - private get captureInterface() { - return <div className="capture-interface"> - <div className="capture-t1"> - <div className="recordButtonOutline" style={{}}> - <div className="recordButtonInner" style={{}}> + return ( + <div className="capture-interface"> + <div className="capture-t1"> + <div className="recordButtonOutline" style={{}}> + <div className="recordButtonInner" style={{}}></div> </div> + Conversation Capture </div> - Conversation Capture - </div> - <div className="capture-t2"> - - </div> - {this.visibilityContent} - {this.linksContent} - <div className="close-button" onClick={this.close}> - <FontAwesomeIcon icon={"times"} color="black" size={"lg"} /> + <div className="capture-t2"></div> + {this.visibilityContent} + {this.linksContent} + <div className="close-button" onClick={this.close}> + <FontAwesomeIcon icon={'times'} color="black" size={'lg'} /> + </div> + {this.closeButtons} </div> - {this.closeButtons} - </div>; - + ); } render() { - return <MainViewModal - contents={this.captureInterface} - isDisplayed={this.isOpen} - interactive={true} - closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: "500px", height: "350px", border: "none", background: "whitesmoke" }} - overlayStyle={{ background: "black" }} - overlayDisplayedOpacity={0.6} - />; + return ( + <MainViewModal + contents={this.captureInterface} + isDisplayed={this.isOpen} + interactive={true} + closeOnExternalClick={this.close} + dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }} + overlayStyle={{ background: 'black' }} + overlayDisplayedOpacity={0.6} + /> + ); } -}
\ No newline at end of file +} diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b2a5fddcd..84efcb966 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -957,7 +957,7 @@ export class CurrentUserUtils { } new LinkManager(); - DocServer.UPDATE_SERVER_CACHE(); + setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500); return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { 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 +} diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx index a209874a3..d4620ae3f 100644 --- a/src/client/util/ScrollBox.tsx +++ b/src/client/util/ScrollBox.tsx @@ -1,21 +1,24 @@ -import React = require("react"); +import React = require('react'); -export class ScrollBox extends React.Component { +export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> { onWheel = (e: React.WheelEvent) => { - if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom + if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + // If the element has a scroll bar, then we don't want the containing collection to zoom e.stopPropagation(); } - } + }; render() { return ( - <div style={{ - overflow: "auto", - width: "100%", - height: "100%", - }} onWheel={this.onWheel}> + <div + style={{ + overflow: 'auto', + width: '100%', + height: '100%', + }} + onWheel={this.onWheel}> {this.props.children} </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index d77633b8d..1acb3ab38 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,29 +1,29 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { intersection } from "lodash"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import Select from "react-select"; -import * as RequestPromise from "request-promise"; -import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util"; -import { Utils } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { CollectionView } from "../views/collections/CollectionView"; -import { DictationOverlay } from "../views/DictationOverlay"; -import { MainViewModal } from "../views/MainViewModal"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import { SearchBox } from "../views/search/SearchBox"; -import { CurrentUserUtils } from "./CurrentUserUtils"; -import { DocumentManager } from "./DocumentManager"; -import { GroupManager, UserOptions } from "./GroupManager"; -import { GroupMemberView } from "./GroupMemberView"; -import { SelectionManager } from "./SelectionManager"; -import "./SharingManager.scss"; -import { LinkManager } from "./LinkManager"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { intersection } from 'lodash'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import Select from 'react-select'; +import * as RequestPromise from 'request-promise'; +import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util'; +import { Utils } from '../../Utils'; +import { DocServer } from '../DocServer'; +import { CollectionView } from '../views/collections/CollectionView'; +import { DictationOverlay } from '../views/DictationOverlay'; +import { MainViewModal } from '../views/MainViewModal'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import { SearchBox } from '../views/search/SearchBox'; +import { CurrentUserUtils } from './CurrentUserUtils'; +import { DocumentManager } from './DocumentManager'; +import { GroupManager, UserOptions } from './GroupManager'; +import { GroupMemberView } from './GroupMemberView'; +import { SelectionManager } from './SelectionManager'; +import './SharingManager.scss'; +import { LinkManager } from './LinkManager'; export interface User { email: string; @@ -44,19 +44,19 @@ interface GroupedOptions { // const DefaultColor = "black"; // used to differentiate between individuals and groups when sharing -const indType = "!indType/"; -const groupType = "!groupType/"; +const indType = '!indType/'; +const groupType = '!groupType/'; -const storage = "data"; +const storage = 'data'; /** * A user who also has a sharing doc. */ interface ValidatedUser { - user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) - sharingDoc: Doc; // document to share/message another user + user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) + sharingDoc: Doc; // document to share/message another user linkDatabase: Doc; - userColor: string; // stored on the sharinDoc, extracted for convenience? + userColor: string; // stored on the sharinDoc, extracted for convenience? } @observer @@ -71,8 +71,8 @@ export class SharingManager extends React.Component<{}> { @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 + @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 private distributeAclsButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the distribute button, used for the position of the popup // if both showUserOptions and showGroupOptions are false then both are displayed @@ -89,7 +89,7 @@ export class SharingManager extends React.Component<{}> { [AclAugment, SharingPermissions.Augment], [AclSelfEdit, SharingPermissions.SelfEdit], [AclEdit, SharingPermissions.Edit], - [AclAdmin, SharingPermissions.Admin] + [AclAdmin, SharingPermissions.Admin], ]); // private get linkVisible() { @@ -105,17 +105,20 @@ export class SharingManager extends React.Component<{}> { this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Augment; }); - } + }; public close = action(() => { this.isOpen = false; this.selectedUsers = null; // resets the list of users and selected users (in the react-select component) TaskCompletionBox.taskCompleted = false; - setTimeout(action(() => { - // this.copied = false; - DictationOverlay.Instance.hasActiveModal = false; - this.targetDoc = undefined; - }), 500); + setTimeout( + action(() => { + // this.copied = false; + DictationOverlay.Instance.hasActiveModal = false; + this.targetDoc = undefined; + }), + 500 + ); }); constructor(props: {}) { @@ -136,7 +139,7 @@ export class SharingManager extends React.Component<{}> { populateUsers = async () => { if (!this.populating) { this.populating = true; - const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; const sharingDocs: ValidatedUser[] = []; const evaluating = raw.map(async user => { @@ -146,7 +149,8 @@ export class SharingManager extends React.Component<{}> { const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId); if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { await DocListCastAsync(linkDatabase.data); - (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => { // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager + (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => { + // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager const a1 = await Cast(link?.anchor1, Doc, null); const a2 = await Cast(link?.anchor2, Doc, null); }); @@ -166,7 +170,7 @@ export class SharingManager extends React.Component<{}> { this.populating = false; }); } - } + }; /** * Shares the document with a user. @@ -179,31 +183,31 @@ export class SharingManager extends React.Component<{}> { const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - return !docs.map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; - } - else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; - } + return !docs + .map(doc => { + doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); + + if (permission === SharingPermissions.None) { + if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; + } else { + if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; + } - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); - else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); - }).some(success => !success); - } + this.setDashboardBackground(doc, permission as SharingPermissions); + if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); + else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); + }) + .some(success => !success); + }; /** * Sets the permission on the target for the group. - * @param group - * @param permission + * @param group + * @param permission */ setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => { - const target = targetDoc || this.targetDoc!; const key = normalizeEmail(StrCast(group.title)); const acl = `acl-${key}`; @@ -212,38 +216,41 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); // ! ensures it returns true if document has been shared successfully, false otherwise - return !docs.map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; - } - else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; - } + return !docs + .map(doc => { + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); + + if (permission === SharingPermissions.None) { + if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; + } else { + if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; + } - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); + this.setDashboardBackground(doc, permission as SharingPermissions); - if (group instanceof Doc) { - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); + if (group instanceof Doc) { + const members: string[] = JSON.parse(StrCast(group.members)); + const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); - // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc - group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]); + // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc + group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : (group.docsShared = new List<Doc>([doc])); - return users.map(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists - }).some(success => !success); - } - }).some(success => success); - } + return users + .map(({ user, sharingDoc }) => { + if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); // remove the doc from the list if it already exists + }) + .some(success => !success); + } + }) + .some(success => success); + }; /** * Shares the documents shared with a group with a new user who has been added to that group. - * @param group - * @param emailId + * @param group + * @param emailId */ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => { const user = this.users.find(({ user: { email } }) => email === emailId)!; @@ -255,30 +262,30 @@ export class SharingManager extends React.Component<{}> { DocListCastAsync(group.docsShared).then(dl => { const filtered = dl?.filter(doc => !userdocs?.includes(doc)); filtered && userdocs?.push(...filtered); - })); + }) + ); } } - } + }; /** * Called from the properties sidebar to change permissions of a user. */ shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => { - if (shareWith !== "Public" && shareWith !== "Override") { - const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); + if (shareWith !== 'Public' && shareWith !== 'Override') { + const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc); }); - } - else { + } else { const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data); docs.forEach(doc => { const isDashboard = dashboards.indexOf(doc) !== -1; if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard); }); } - } + }; /** * Sets the background of the Dashboard if it has been shared as a visual indicator @@ -287,22 +294,21 @@ export class SharingManager extends React.Component<{}> { if (Doc.IndexOf(doc, DocListCast(CurrentUserUtils.MyDashboards.data)) !== -1) { if (permission !== SharingPermissions.None) { doc.isShared = true; - doc.backgroundColor = "green"; - } - else { + doc.backgroundColor = 'green'; + } else { const acls = doc[DataSym][AclSym]; - if (Object.keys(acls).every(key => key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key]))) { + if (Object.keys(acls).every(key => (key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key])))) { doc.isShared = undefined; doc.backgroundColor = undefined; } } } - } + }; /** * Removes the documents shared with a user through a group when the user is removed from the group. - * @param group - * @param emailId + * @param group + * @param emailId */ removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; @@ -315,11 +321,11 @@ export class SharingManager extends React.Component<{}> { }) ); } - } + }; /** * Removes a group's permissions from documents that have been shared with it. - * @param group + * @param group */ removeGroup = (group: Doc) => { if (group.docsShared) { @@ -335,9 +341,7 @@ export class SharingManager extends React.Component<{}> { users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc)); }); } - } - - + }; // private setExternalSharing = (permission: string) => { // const targetDoc = this.targetDoc; @@ -367,23 +371,23 @@ export class SharingManager extends React.Component<{}> { */ private sharingOptions(uniform: boolean, override?: boolean) { const dropdownValues: string[] = Object.values(SharingPermissions); - if (!uniform) dropdownValues.unshift("-multiple-"); - if (override) dropdownValues.unshift("None"); - return dropdownValues.filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => - ( - <option key={permission} value={permission}> - {permission} - </option> - ) - ); + if (!uniform) dropdownValues.unshift('-multiple-'); + if (override) dropdownValues.unshift('None'); + return dropdownValues + .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)) + .map(permission => ( + <option key={permission} value={permission}> + {permission} + </option> + )); } private focusOn = (contents: string) => { - const title = this.targetDoc ? StrCast(this.targetDoc.title) : ""; + const title = this.targetDoc ? StrCast(this.targetDoc.title) : ''; const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc]; return ( <span - className={"focus-span"} + className={'focus-span'} title={title} onClick={() => { let context: Opt<CollectionView>; @@ -404,12 +408,11 @@ export class SharingManager extends React.Component<{}> { this.dialogueBoxOpacity = 1; this.overlayOpacity = 0.4; } - })} - > + })}> {contents} </span> ); - } + }; /** * Handles changes in the users selected in react-select @@ -417,7 +420,7 @@ export class SharingManager extends React.Component<{}> { @action handleUsersChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; - } + }; /** * Handles changes in the permission chosen to share with someone with @@ -425,7 +428,7 @@ export class SharingManager extends React.Component<{}> { @action handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => { this.permissions = event.currentTarget.value as SharingPermissions; - } + }; /** * Calls the relevant method for sharing, displays the popup, and resets the relevant variables. @@ -436,8 +439,7 @@ export class SharingManager extends React.Component<{}> { this.selectedUsers.forEach(user => { if (user.value.includes(indType)) { this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); - } - else { + } else { this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); } }); @@ -445,13 +447,16 @@ export class SharingManager extends React.Component<{}> { const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); TaskCompletionBox.popupX = left - 1.5 * width; TaskCompletionBox.popupY = top - 1.5 * height; - TaskCompletionBox.textDisplayed = "Document shared!"; + TaskCompletionBox.textDisplayed = 'Document shared!'; TaskCompletionBox.taskCompleted = true; - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); this.selectedUsers = null; } - } + }; // distributeOverCollection = (targetDoc?: Doc) => { // const target = targetDoc || this.targetDoc!; @@ -471,7 +476,7 @@ export class SharingManager extends React.Component<{}> { const { email: e1 } = u1.user; const { email: e2 } = u2.user; return e1 < e2 ? -1 : e1 === e2 ? 0 : 1; - } + }; /** * Sorting algorithm to sort groups. @@ -480,7 +485,7 @@ export class SharingManager extends React.Component<{}> { const g1 = StrCast(group1.title); const g2 = StrCast(group2.title); return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; - } + }; /** * @returns the main interface of the SharingManager. @@ -488,28 +493,29 @@ export class SharingManager extends React.Component<{}> { @computed get sharingInterface() { TraceMobx(); const groupList = GroupManager.Instance?.allGroups || []; - const sortedUsers = this.users.slice().sort(this.sortUsers).map(({ user: { email } }) => ({ label: email, value: indType + email })); - const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) })); + const sortedUsers = this.users + .slice() + .sort(this.sortUsers) + .map(({ user: { email } }) => ({ label: email, value: indType + email })); + const sortedGroups = groupList + .slice() + .sort(this.sortGroups) + .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) })); // the next block handles the users shown (individuals/groups/both) const options: GroupedOptions[] = []; 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 }); + 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.slice().sort(this.sortUsers) : this.individualSort === "descending" ? this.users.slice().sort(this.sortUsers).reverse() : this.users; - const groups = this.groupSort === "ascending" ? groupList.slice().sort(this.sortGroups) : this.groupSort === "descending" ? groupList.slice().sort(this.sortGroups).reverse() : groupList; + const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users; + const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList; // handles the case where multiple documents are selected - let docs = SelectionManager.Views().length < 2 ? - [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] - : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]); + let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym])); if (this.myDocAcls) { const newDocs: Doc[] = []; @@ -524,107 +530,78 @@ export class SharingManager extends React.Component<{}> { const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin); // users in common between all docs - const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))); + const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])))); // the list of users shared with - const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, linkDatabase, sharingDoc, userColor }) => { - const userKey = `acl-${normalizeEmail(user.email)}`; - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]); - const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-"; - - return !permissions ? (null) : ( - <div - key={userKey} - className={"container"} - > - <span className={"padding"}>{user.email}</span> - <div className="edit-actions"> - {admin || this.myDocAcls ? ( - <select - className={"permissions-dropdown"} - value={permissions} - onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)} - > - {this.sharingOptions(uniform)} - </select> - ) : ( - <div className={"permissions-dropdown"}> - {permissions} - </div> - )} + const userListContents: (JSX.Element | null)[] = users + .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) + .map(({ user, linkDatabase, sharingDoc, userColor }) => { + const userKey = `acl-${normalizeEmail(user.email)}`; + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey])); + const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + + return !permissions ? null : ( + <div key={userKey} className={'container'}> + <span className={'padding'}>{user.email}</span> + <div className="edit-actions"> + {admin || this.myDocAcls ? ( + <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}> + {this.sharingOptions(uniform)} + </select> + ) : ( + <div className={'permissions-dropdown'}>{permissions}</div> + )} + </div> </div> - </div> - ); - }); + ); + }); // checks if every doc has the same author const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author); // the owner of the doc and the current user are placed at the top of the user list. userListContents.unshift( - sameAuthor ? - ( - <div - key={"owner"} - className={"container"} - > - <span className={"padding"}>{targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author}</span> - <div className="edit-actions"> - <div className={"permissions-dropdown"}> - Owner - </div> - </div> + sameAuthor ? ( + <div key={'owner'} className={'container'}> + <span className={'padding'}>{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : targetDoc?.author}</span> + <div className="edit-actions"> + <div className={'permissions-dropdown'}>Owner</div> </div> - ) : null, - sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? - ( - <div - key={"me"} - className={"container"} - > - <span className={"padding"}>Me</span> - <div className="edit-actions"> - <div className={"permissions-dropdown"}> - {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"} - </div> - </div> + </div> + ) : null, + sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? ( + <div key={'me'} className={'container'}> + <span className={'padding'}>Me</span> + <div className="edit-actions"> + <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : '-multiple-'}</div> </div> - ) : null + </div> + ) : null ); - // the list of groups shared with - const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true); - groupListMap.unshift({ title: "Public" });//, { title: "ALL" }); + const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true)); + groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" }); const groupListContents = groupListMap.map(group => { const groupKey = `acl-${StrCast(group.title)}`; - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); - const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : "-multiple-"; - - return !permissions ? (null) : ( - <div - key={groupKey} - className={"container"} - > - <div className={"padding"}>{group.title}</div> - {group instanceof Doc ? - (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}> - <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> - </div>) - : (null)} + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey])); + const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; + + return !permissions ? null : ( + <div key={groupKey} className={'container'}> + <div className={'padding'}>{StrCast(group.title)}</div> + {group instanceof Doc ? ( + <div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}> + <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> + </div> + ) : null} <div className="edit-actions"> {admin || this.myDocAcls ? ( - <select - className={"permissions-dropdown"} - value={permissions} - onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)} - > - {this.sharingOptions(uniform, group.title === "Override")} + <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}> + {this.sharingOptions(uniform, group.title === 'Override')} </select> ) : ( - <div className={"permissions-dropdown"}> - {permissions} - </div> + <div className={'permissions-dropdown'}>{permissions}</div> )} </div> </div> @@ -632,102 +609,95 @@ export class SharingManager extends React.Component<{}> { }); return ( - <div className={"sharing-interface"}> - {GroupManager.Instance?.currentGroup ? - <GroupMemberView - group={GroupManager.Instance.currentGroup} - onCloseButtonClick={action(() => GroupManager.Instance.currentGroup = undefined)} - /> : - null} + <div className={'sharing-interface'}> + {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null} <div className="sharing-contents"> - <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p> - <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <p className={'share-title'}> + <b>Share </b> + {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} + </p> + <div className={'close-button'} onClick={this.close}> + <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> </div> {/* {this.linkVisible ? <div> {this.sharingUrl} </div> : (null)} */} - {<div className="share-container"> - <div className="share-setup"> - <Select - className="user-search" - placeholder="Enter user or group name..." - isMulti - isSearchable - closeMenuOnSelect={false} - options={options} - onKeyDown={e => e.stopPropagation()} - onChange={this.handleUsersChange} - value={this.selectedUsers} - styles={{ - indicatorSeparator: () => ({ - visibility: "hidden" - }) - }} - /> - <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}> - {this.sharingOptions(true)} - </select> - <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> - 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 className="share-container"> + <div className="share-setup"> + <Select + className="user-search" + placeholder="Enter user or group name..." + isMulti + isSearchable + closeMenuOnSelect={false} + options={options} + onKeyDown={e => e.stopPropagation()} + onChange={this.handleUsersChange} + value={this.selectedUsers} + styles={{ + indicatorSeparator: () => ({ + visibility: 'hidden', + }), + }} + /> + <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}> + {this.sharingOptions(true)} + </select> + <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> + 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 className="acl-container"> - {Doc.noviceMode ? (null) : - <div className="layoutDoc-acls"> - <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label> - </div>} + <div className="acl-container"> + {Doc.noviceMode ? null : ( + <div className="layoutDoc-acls"> + <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label> + </div> + )} + </div> </div> - </div> } <div className="main-container"> - <div className={"individual-container"}> - <div - className="user-sort" - onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> - Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> - : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> - : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />} - </div> - <div className={"users-list"}> - {userListContents} + <div className={'individual-container'}> + <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> + Individuals{' '} + {this.individualSort === 'ascending' ? ( + <FontAwesomeIcon icon={'caret-up'} size={'xs'} /> + ) : this.individualSort === 'descending' ? ( + <FontAwesomeIcon icon={'caret-down'} size={'xs'} /> + ) : ( + <FontAwesomeIcon icon={'caret-right'} size={'xs'} /> + )} </div> + <div className={'users-list'}>{userListContents}</div> </div> - <div className={"group-container"}> - <div - className="user-sort" - onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Groups {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={"groups-list"}> - {groupListContents} + <div className={'group-container'}> + <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> + Groups{' '} + {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={'groups-list'}>{groupListContents}</div> </div> </div> - </div> </div> ); } render() { - return <MainViewModal - contents={this.sharingInterface} - isDisplayed={this.isOpen} - interactive={true} - dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} - overlayDisplayedOpacity={this.overlayOpacity} - closeOnExternalClick={this.close} - />; + return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />; } -}
\ No newline at end of file +} |
