aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/SharingManager.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-04-21 19:03:49 -0400
committerbobzel <zzzman@gmail.com>2024-04-21 19:03:49 -0400
commit939e18624af4252551f38c43335ee8ef0acd144c (patch)
treed4e7a8dd4db05737ec1343ff8d80611537bde65b /src/client/util/SharingManager.tsx
parent57d9c12d6b88d6814e468aca93b9bf809eabd9ce (diff)
more lint cleanup
Diffstat (limited to 'src/client/util/SharingManager.tsx')
-rw-r--r--src/client/util/SharingManager.tsx725
1 files changed, 396 insertions, 329 deletions
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index ade4cc218..6676e4e03 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { concat, intersection } from 'lodash';
@@ -65,7 +68,10 @@ interface ValidatedUser {
@observer
export class SharingManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SharingManager;
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
+ private populating: boolean = false; // whether the list of users is populating or not
@observable private isOpen = false; // whether the SharingManager modal is open or not
@observable public users: ValidatedUser[] = []; // the list of users with sharing docs
@observable private targetDoc: Doc | undefined = undefined; // the document being shared
@@ -77,11 +83,9 @@ export class SharingManager extends React.Component<{}> {
@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 populating: boolean = false; // whether the list of users is populating or not
@observable private upgradeNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is upgrade all
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
@@ -90,33 +94,6 @@ export class SharingManager extends React.Component<{}> {
// return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
// }
- public open = (target?: DocumentView, target_doc?: Doc) => {
- this.populateUsers();
- runInAction(() => {
- this.targetDocView = target;
- this.targetDoc = target_doc || target?.Document;
- DictationOverlay.Instance.hasActiveModal = true;
- this.isOpen = this.targetDoc !== undefined;
- this.permissions = SharingPermissions.Augment;
- this.upgradeNested = true;
- });
- };
-
- 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
- );
- this.layoutDocAcls = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
@@ -131,226 +108,6 @@ export class SharingManager extends React.Component<{}> {
}
/**
- * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
- */
- populateUsers = async () => {
- if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
- this.populating = true;
- const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
- const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail());
- runInAction(() => {
- FieldLoader.ServerLoadStatus.message = 'users';
- });
- const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
- raw.map(
- action((newUser: User) => {
- const sharingDoc = docs[newUser.sharingDocumentId];
- const linkDatabase = docs[newUser.linkDatabaseId];
- if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
- if (!this.users.find(user => user.user.email === newUser.email)) {
- this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- //LinkManager.addLinkDB(linkDatabase);
- }
- }
- })
- );
- this.populating = false;
- }
- };
-
- /**
- * Shares the document with a user.
- */
- setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
- const { user, sharingDoc } = recipient;
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(user.email)}`;
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
- if (permission !== SharingPermissions.None) {
- Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
- } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
- });
- }, 'set Doc permissions');
-
- /**
- * Sets the permission on the target for the group.
- * @param group
- * @param permission
- */
- setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
-
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
-
- if (group instanceof Doc) {
- Doc.AddDocToList(group, 'docsShared', doc);
-
- this.users
- .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
- .forEach(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None)
- Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
- });
- }
- });
- }, 'set group permissions');
-
- /**
- * 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, retry: boolean = true) => {
- const user = this.users.find(({ user: { email } }) => email === emailId)!;
- const self = this;
- if (group.docsShared) {
- if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
- else {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
- filtered && userdocs?.push(...filtered);
- })
- );
- DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
- filtered && userdocs?.push(...filtered);
- })
- );
- }
- }
- };
-
- /**
- * Called from the properties sidebar to change permissions of a user.
- */
- shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
- if (layout) this.layoutDocAcls = true;
- if (shareWith !== 'Guest') {
- const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail() : shareWith));
- docs.forEach(doc => {
- if (user) this.setInternalSharing(user, permission, doc);
- else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
- });
- } else {
- docs.forEach(doc => {
- if (GetEffectiveAcl(doc) === AclAdmin) {
- distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
- }
- });
- }
- this.layoutDocAcls = false;
- }, 'sidebar set permissions');
-
- /**
- * 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 && user) {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
- userdocs?.splice(0, userdocs.length, ...remaining);
- })
- );
- DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
- userdocs?.splice(0, userdocs.length, ...remaining);
- })
- );
- }
- };
-
- /**
- * Removes a group's permissions from documents that have been shared with it.
- * @param group
- */
- removeGroup = (group: Doc) => {
- if (group.docsShared) {
- DocListCast(group.docsShared).forEach(doc => {
- const acl = `acl-${StrCast(group.title)}`;
- distributeAcls(acl, SharingPermissions.None, doc);
-
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
-
- users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
- });
- }
- };
-
- // private setExternalSharing = (permission: string) => {
- // const targetDoc = this.targetDoc;
- // if (!targetDoc) {
- // return;
- // }
- // targetDoc["acl-" + PublicKey] = permission;
- // }s
-
- /**
- * Copies the Public sharing url to the user's clipboard.
- */
- private copyURL = (e: any) => {
- ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
- };
-
- /**
- * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
- */
- private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
- const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
- if (!uniform) dropdownValues.unshift('-multiple-');
- return dropdownValues.map(permission => (
- <option key={permission} value={permission}>
- {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
- </option>
- ));
- }
-
- private focusOn = (contents: string) => {
- 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"
- title={title}
- onClick={() => {
- if (this.targetDoc && this.targetDocView && docs.length === 1) {
- DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
- }
- }}
- onPointerEnter={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.BrushDoc(doc));
- this.dialogueBoxOpacity = 0.1;
- this.overlayOpacity = 0.1;
- }
- })}
- onPointerLeave={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
- this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
- }
- })}>
- {contents}
- </span>
- );
- };
-
- /**
* Handles changes in the users selected in react-select
*/
@action
@@ -369,57 +126,6 @@ export class SharingManager extends React.Component<{}> {
);
/**
- * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
- */
- share = undoable(
- action(() => {
- if (this.selectedUsers) {
- this.selectedUsers.forEach(user => {
- if (user.value.includes(indType)) {
- this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
- } else {
- this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
- }
- });
-
- if (this.shareDocumentButtonRef.current) {
- 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.taskCompleted = true;
- setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
- 2000
- );
- }
-
- this.layoutDocAcls = false;
- this.selectedUsers = null;
- }
- }),
- 'share Doc'
- );
-
- /**
- * Sorting algorithm to sort users.
- */
- sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
- const { email: e1 } = u1.user;
- const { email: e2 } = u2.user;
- return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
- };
-
- /**
- * Sorting algorithm to sort groups.
- */
- sortGroups = (group1: Doc, group2: Doc) => {
- 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.
*/
@computed get sharingInterface() {
@@ -477,8 +183,8 @@ export class SharingManager extends React.Component<{}> {
permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
return !permissions ? null : (
- <div key={userKey} className={'container'}>
- <span className={'padding'}>{user.email}</span>
+ <div key={userKey} className="container">
+ <span className="padding">{user.email}</span>
<div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
@@ -504,16 +210,16 @@ export class SharingManager extends React.Component<{}> {
// const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
- <div key={'owner'} className={'container'}>
+ <div key="owner" className="container">
<span className="padding">{targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>Owner</div>
+ <div className="permissions-dropdown">Owner</div>
</div>
</div>
) : null,
sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
- <div key={'me'} className={'container'}>
- <span className={'padding'}>Me</span>
+ <div key="me" className="container">
+ <span className="padding">Me</span>
<div className="edit-actions">
<div className={`permissions-dropdown-${curUserPermission}`}>
{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
@@ -526,18 +232,27 @@ export class SharingManager extends React.Component<{}> {
// 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: 'Guest' }); //, { title: "ALL" });
+ groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- let groupKey = `acl-${StrCast(group.title)}`;
+ const groupKey = `acl-${StrCast(group.title)}`;
const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
- <div key={groupKey} className={'container'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- <div className={'padding'}>{StrCast(group.title)}</div>
+ <div key={groupKey} className="container" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="padding">{StrCast(group.title)}</div>
&nbsp;
- {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
- <div className={'edit-actions'}>
+ {group instanceof Doc ? (
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={SettingsManager.userColor}
+ onClick={action(() => {
+ GroupManager.Instance.currentGroup = group;
+ })}
+ />
+ ) : null}
+ <div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
{this.sharingOptions(uniform, group.title === 'Guest')}
@@ -554,7 +269,14 @@ 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}
+ {GroupManager.Instance?.currentGroup ? (
+ <GroupMemberView
+ group={GroupManager.Instance.currentGroup}
+ onCloseButtonClick={action(() => {
+ GroupManager.Instance.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div
className="sharing-contents"
style={{
@@ -563,16 +285,16 @@ export class SharingManager extends React.Component<{}> {
}}>
<p className="share-title" style={{ color: SettingsManager.userColor }}>
<div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
- <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
+ <FontAwesomeIcon icon="question-circle" size="sm" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
</div>
<b>Share </b>
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
<div className="share-copy-link">
- <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
+ <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SettingsManager.userColor} />
</div>
{admin ? (
<div className="share-container">
@@ -614,19 +336,45 @@ export class SharingManager extends React.Component<{}> {
</select>
</div>
<div className="share-button">
- <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
+ <Button text="SHARE" type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
</div>
</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>
+ <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.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> <label>Upgrade Nested </label>
- <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.upgradeNested = !this.upgradeNested;
+ })}
+ checked={this.upgradeNested}
+ />{' '}
+ <label>Upgrade Nested </label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />{' '}
+ <label>Layout</label>
</div>
)}
</div>
@@ -635,14 +383,25 @@ export class SharingManager extends React.Component<{}> {
<div className="share-container">
<div className="acl-container">
<div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />{' '}
+ <label>Layout</label>
</div>
</div>
</div>
)}
<div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}>
- <div className={'individual-container'}>
- <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
+ <div className="individual-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-individual">
Individuals
<IconButton
@@ -654,11 +413,15 @@ export class SharingManager extends React.Component<{}> {
</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'))}>
+ <div className="group-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-group">
Groups
- <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
+ <IconButton icon={<FontAwesomeIcon icon="info-circle" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
<IconButton
icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />}
size={Size.XSMALL}
@@ -666,7 +429,7 @@ export class SharingManager extends React.Component<{}> {
/>
</div>
</div>
- <div className={'groups-list'}>{groupListContents}</div>
+ <div className="groups-list">{groupListContents}</div>
</div>
</div>
</div>
@@ -674,7 +437,311 @@ export class SharingManager extends React.Component<{}> {
);
}
+ /**
+ * Shares the document with a user.
+ */
+ setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
+ const { user, sharingDoc } = recipient;
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl-${normalizeEmail(user.email)}`;
+ const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+ if (permission !== SharingPermissions.None) {
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
+ } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
+ });
+ }, 'set Doc permissions');
+
+ /**
+ * Sets the permission on the target for the group.
+ * @param group
+ * @param permission
+ */
+ setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
+
+ const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+
+ if (group instanceof Doc) {
+ Doc.AddDocToList(group, 'docsShared', doc);
+
+ this.users
+ .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
+ .forEach(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None)
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
+ });
+ }
+ });
+ }, 'set group permissions');
+ /**
+ * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
+ */
+ populateUsers = async () => {
+ if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
+ this.populating = true;
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
+ const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail());
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.message = 'users';
+ });
+ const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
+ raw.map(
+ action((newUser: User) => {
+ const sharingDoc = docs[newUser.sharingDocumentId];
+ const linkDatabase = docs[newUser.linkDatabaseId];
+ if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
+ if (!this.users.find(user => user.user.email === newUser.email)) {
+ this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
+ // LinkManager.addLinkDB(linkDatabase);
+ }
+ }
+ })
+ );
+ this.populating = false;
+ }
+ };
+
+ // eslint-disable-next-line react/sort-comp
+ 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
+ );
+ this.layoutDocAcls = false;
+ });
+
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
+ this.populateUsers();
+ runInAction(() => {
+ this.targetDocView = target;
+ this.targetDoc = targetDoc || target?.Document;
+ DictationOverlay.Instance.hasActiveModal = true;
+ this.isOpen = this.targetDoc !== undefined;
+ this.permissions = SharingPermissions.Augment;
+ this.upgradeNested = true;
+ });
+ };
+
+ /**
+ * Shares the documents shared with a group with a new user who has been added to that group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
+ const user = this.users.find(({ user: { email } }) => email === emailId)!;
+ const self = this;
+ if (group.docsShared) {
+ if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
+ else {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ }
+ }
+ };
+
+ /**
+ * Called from the properties sidebar to change permissions of a user.
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
+ if (layout) this.layoutDocAcls = true;
+ if (shareWith !== 'Guest') {
+ const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail() : shareWith));
+ docs.forEach(doc => {
+ if (user) this.setInternalSharing(user, permission, doc);
+ else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
+ });
+ } else {
+ docs.forEach(doc => {
+ if (GetEffectiveAcl(doc) === AclAdmin) {
+ distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
+ }
+ });
+ }
+ this.layoutDocAcls = false;
+ }, 'sidebar set permissions');
+
+ /**
+ * Removes the documents shared with a user through a group when the user is removed from the group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeMember = (group: Doc, emailId: string) => {
+ const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
+
+ if (group.docsShared && user) {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
+ }
+ };
+
+ /**
+ * Removes a group's permissions from documents that have been shared with it.
+ * @param group
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeGroup = (group: Doc) => {
+ if (group.docsShared) {
+ DocListCast(group.docsShared).forEach(doc => {
+ const acl = `acl-${StrCast(group.title)}`;
+ distributeAcls(acl, SharingPermissions.None, doc);
+
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+
+ users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
+ });
+ }
+ };
+
+ // private setExternalSharing = (permission: string) => {
+ // const targetDoc = this.targetDoc;
+ // if (!targetDoc) {
+ // return;
+ // }
+ // targetDoc["acl-" + PublicKey] = permission;
+ // }s
+
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = () => {
+ ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
+ };
+
+ private focusOn = (contents: string) => {
+ 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"
+ title={title}
+ onClick={() => {
+ if (this.targetDoc && this.targetDocView && docs.length === 1) {
+ DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+ }
+ }}
+ onPointerEnter={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.BrushDoc(doc));
+ this.dialogueBoxOpacity = 0.1;
+ this.overlayOpacity = 0.1;
+ }
+ })}
+ onPointerLeave={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+ this.dialogueBoxOpacity = 1;
+ this.overlayOpacity = 0.4;
+ }
+ })}>
+ {contents}
+ </span>
+ );
+ };
+
+ /**
+ * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
+ */
+ share = undoable(
+ action(() => {
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
+ } else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ if (this.shareDocumentButtonRef.current) {
+ 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.taskCompleted = true;
+ setTimeout(
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
+ 2000
+ );
+ }
+
+ this.layoutDocAcls = false;
+ this.selectedUsers = null;
+ }
+ }),
+ 'share Doc'
+ );
+
+ /**
+ * Sorting algorithm to sort users.
+ */
+ sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
+ const { email: e1 } = u1.user;
+ const { email: e2 } = u2.user;
+ return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
+ };
+
+ /**
+ * Sorting algorithm to sort groups.
+ */
+ sortGroups = (group1: Doc, group2: Doc) => {
+ const g1 = StrCast(group1.title);
+ const g2 = StrCast(group2.title);
+ return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
+ };
+ /**
+ * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
+ */
+ private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
+ if (!uniform) dropdownValues.unshift('-multiple-');
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
+ }
+
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 dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
}
}