aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/SharingManager.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/SharingManager.tsx')
-rw-r--r--src/client/util/SharingManager.tsx228
1 files changed, 157 insertions, 71 deletions
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 97e64ab71..534a9cfde 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,15 +1,16 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { intersection } from 'lodash';
+import { IconButton, Size } from 'browndash-components';
+import { concat, 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 { Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
-import { AclAdmin, AclPrivate, DocAcl, AclUnset, DocData } from '../../fields/DocSymbols';
+import { Doc, DocCastAsync, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { AclAdmin, AclPrivate, AclUnset, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
-import { NumCast, StrCast } from '../../fields/Types';
+import { DocCast, NumCast, StrCast } from '../../fields/Types';
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
import { Utils } from '../../Utils';
import { DocServer } from '../DocServer';
@@ -23,6 +24,7 @@ import { GroupManager, UserOptions } from './GroupManager';
import { GroupMemberView } from './GroupMemberView';
import { SelectionManager } from './SelectionManager';
import './SharingManager.scss';
+import { Docs } from '../documents/Documents';
export interface User {
email: string;
@@ -79,6 +81,7 @@ export class SharingManager extends React.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 layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
+ @observable private overrideNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private
@observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
// private get linkVisible() {
@@ -108,6 +111,8 @@ export class SharingManager extends React.Component<{}> {
}),
500
);
+ this.layoutDocAcls = false;
+ this.overrideNested = false;
});
constructor(props: {}) {
@@ -157,9 +162,20 @@ export class SharingManager extends React.Component<{}> {
const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
+ // setting the same acl for a docs within the doc being shared if they haven't been set yet
+ // or if the 'Override Nested' checkbox is selected
+ var childDocs = DocListCast(target.data);
+ childDocs.map(doc => {
+ if (this.overrideNested || doc[acl]==undefined){
+ this.setInternalSharing(recipient, permission, doc);
+ }
+ });
+
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 => (this.layoutDocAcls ? doc : doc[DocData]))
+ .map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc)))
.map(doc => {
doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
@@ -168,9 +184,7 @@ export class SharingManager extends React.Component<{}> {
} else {
if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
}
-
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);
return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc);
@@ -186,11 +200,21 @@ export class SharingManager extends React.Component<{}> {
setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
const target = targetDoc || this.targetDoc!;
const key = normalizeEmail(StrCast(group.title));
- const acl = `acl-${key}`;
+ let acl = `acl-${key}`;
const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
+ // setting the same acl for a docs within the doc being shared if they haven't been set yet
+ // or if the 'Override Private' checkbox is selected
+ var childDocs = DocListCast(target.data);
+ childDocs.map(doc => {
+ if (this.overrideNested || doc[acl]==undefined){
+ this.setInternalGroupSharing(group, permission, doc);
+ }
+ });
+
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
+ if (acl == 'acl-Public' && this.layoutDocAcls) acl = 'acl-Public-layout';
// ! ensures it returns true if document has been shared successfully, false otherwise
return !docs
.map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
@@ -248,7 +272,8 @@ export class SharingManager extends React.Component<{}> {
/**
* Called from the properties sidebar to change permissions of a user.
*/
- shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
+ shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
+ if (layout) this.layoutDocAcls = true;
if (shareWith !== 'Public' && shareWith !== 'Override') {
const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
docs.forEach(doc => {
@@ -259,10 +284,17 @@ export class SharingManager extends React.Component<{}> {
const dashboards = DocListCast(Doc.MyDashboards.data);
docs.forEach(doc => {
const isDashboard = dashboards.indexOf(doc) !== -1;
- if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
+ if (this.overrideNested) {
+ this.shareFromPropertiesSidebar(shareWith, permission, DocListCast(doc.data), layout);
+ }
+ if (GetEffectiveAcl(doc) === AclAdmin) {
+ if ( shareWith == 'Public' && layout) shareWith = 'Public-layout';
+ distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
+ }
this.setDashboardBackground(doc, permission as SharingPermissions);
});
}
+ this.layoutDocAcls = false;
};
/**
@@ -316,6 +348,7 @@ export class SharingManager extends React.Component<{}> {
const acl = `acl-${StrCast(group.title)}`;
const isDashboard = dashboards.indexOf(doc) !== -1;
distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined, isDashboard);
+ distributeAcls(acl, SharingPermissions.None, Doc.GetProto(doc), undefined, undefined, isDashboard);
const members: string[] = JSON.parse(StrCast(group.members));
const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -331,22 +364,14 @@ export class SharingManager extends React.Component<{}> {
// return;
// }
// targetDoc["acl-" + PublicKey] = permission;
- // }
+ // }s
- // private get sharingUrl() {
- // if (!this.targetDoc) {
- // return undefined;
- // }
- // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
- // return `${baseUrl}?sharing=true`;
- // }
-
- // copy = action(() => {
- // if (this.sharingUrl) {
- // Utils.CopyText(this.sharingUrl);
- // this.copied = true;
- // }
- // });
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = (e: any) => {
+ Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
+ };
/**
* Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
@@ -355,13 +380,11 @@ export class SharingManager extends React.Component<{}> {
const dropdownValues: string[] = Object.values(SharingPermissions);
if (!uniform) dropdownValues.unshift('-multiple-');
if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
- return dropdownValues
- .filter(permission => !Doc.noviceMode || ![SharingPermissions.SelfEdit].includes(permission as any))
- .map(permission => (
- <option key={permission} value={permission}>
- {permission}
- </option>
- ));
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
}
private focusOn = (contents: string) => {
@@ -435,6 +458,8 @@ export class SharingManager extends React.Component<{}> {
2000
);
+ this.layoutDocAcls = false;
+ this.overrideNested = false;
this.selectedUsers = null;
}
};
@@ -464,6 +489,7 @@ export class SharingManager extends React.Component<{}> {
if (!this.targetDoc) return null;
TraceMobx();
const groupList = GroupManager.Instance?.allGroups || [];
+
const sortedUsers = this.users
.slice()
.sort(this.sortUsers)
@@ -494,7 +520,7 @@ export class SharingManager extends React.Component<{}> {
docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin);
}
- const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
+ const targetDoc: Doc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
// tslint:disable-next-line: no-unnecessary-callback-wrapper
const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
@@ -505,22 +531,33 @@ export class SharingManager extends React.Component<{}> {
// 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))
+ // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ .filter(({ user }) => docs[0]?.author !== user.email)
.map(({ user, linkDatabase, sharingDoc, userColor }) => {
const userKey = `acl-${normalizeEmail(user.email)}`;
const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
- const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
-
+ // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+ let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(Doc.GetProto(targetDoc)[userKey])) : StrCast(targetDoc[userKey]);
+ if (this.layoutDocAcls){
+ if (targetDoc[DocAcl][userKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name;
+ else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]);
+ else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[userKey]) : '-multiple-';
+ }
+ else 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)}>
+ <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}>
{this.sharingOptions(uniform)}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -531,6 +568,15 @@ export class SharingManager extends React.Component<{}> {
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.
+ const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
+ var curUserPermission;
+ if (this.layoutDocAcls){
+ if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name;
+ else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]);
+ else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]);
+ }
+ else curUserPermission = StrCast(targetDoc[userKey]);
+ // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
<div key={'owner'} className={'container'}>
@@ -544,7 +590,10 @@ export class SharingManager extends React.Component<{}> {
<div key={'me'} className={'container'}>
<span className={'padding'}>Me</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-'}</div>
+ <div className={`permissions-dropdown-${curUserPermission}`}>
+ {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
+ &nbsp;
+ </div>
</div>
</div>
) : null
@@ -554,27 +603,42 @@ export class SharingManager extends React.Component<{}> {
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)}`;
+ let groupKey = `acl-${StrCast(group.title)}`;
const uniform = docs
.map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
.every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey]));
- const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
+ // const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
+ let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][groupKey] ? HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name : StrCast(Doc.GetProto(targetDoc)[groupKey])) : StrCast(targetDoc[groupKey]);
+ if (this.layoutDocAcls){
+ if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout';
+ if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name;
+ else{
+ if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public';
+ if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]);
+ else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-';
+ }
+ }
+ else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
<div key={groupKey} className={'container'}>
<div className={'padding'}>{StrCast(group.title)}</div>
+ &nbsp;
{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">
+ <div className={"edit-actions"}>
{admin || this.myDocAcls ? (
- <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+ <select className={`permissions-dropdown-${permissions}`} 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}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -584,19 +648,22 @@ export class SharingManager extends React.Component<{}> {
<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'}>
+ <p className="share-title">
+ <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} >
+ <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')}/>
+ </div>
<b>Share </b>
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
+ <button className="share-copy-link" onClick={this.copyURL}>
+ <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/>
+ &nbsp;
+ Copy Public URL
+ </button>
<div className={'close-button'} onClick={this.close}>
<FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
- {/* {this.linkVisible ?
- <div>
- {this.sharingUrl}
- </div> :
- (null)} */}
- {
+ {admin ? (
<div className="share-container">
<div className="share-setup">
<Select
@@ -615,9 +682,11 @@ export class SharingManager extends React.Component<{}> {
}),
}}
/>
- <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
- {this.sharingOptions(true)}
- </select>
+ <div className='permissions-select'>
+ <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
+ {this.sharingOptions(true)}
+ </select>
+ </div>
<button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
Share
</button>
@@ -630,36 +699,53 @@ export class SharingManager extends React.Component<{}> {
<div className="acl-container">
{Doc.noviceMode ? null : (
<div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} /> <label>Override Nested </label>
<input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
</div>
)}
</div>
</div>
- }
+ ) : (
+ <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>
+ </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 className='title-individual'>
+ Individuals &nbsp;
+ {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>
<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 className='title-group'>
+ Groups &nbsp;
+ <div className="group-info" onClick={action(() => GroupManager.Instance?.open())}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
+ </div>
+ &nbsp;
+ {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>
<div className={'groups-list'}>{groupListContents}</div>
</div>