diff options
Diffstat (limited to 'src/client/util/SharingManager.tsx')
-rw-r--r-- | src/client/util/SharingManager.tsx | 232 |
1 files changed, 161 insertions, 71 deletions
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 97e64ab71..b557dd5d6 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 overridePrevious: 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.overridePrevious = 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 previous' checkbox is selected + var childDocs = DocListCast(target.data); + childDocs.map(doc => { + if (this.overridePrevious || 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.overridePrevious || 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.overridePrevious) { + 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.overridePrevious = 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)} + + </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-'} + + </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> + {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)} + + </div> )} </div> </div> @@ -584,19 +648,26 @@ 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"> + <IconButton size={Size.SMALL} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} /> <b>Share </b> {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} + {/* <button className="share-copy-link" onClick={this.copyURL}> + <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/> + + Copy Public URL + </button> */} + {/* <IconButton size={Size.SMALL} tooltip="Copy Public URL" onClick={this.copyURL} icon={<FontAwesomeIcon icon="copy" />} /> */} </p> + <button className="share-copy-link" onClick={this.copyURL}> + <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/> + + 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 +686,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 +703,53 @@ export class SharingManager extends React.Component<{}> { <div className="acl-container"> {Doc.noviceMode ? null : ( <div className="layoutDoc-acls"> + <input type="checkbox" onChange={action(() => (this.overridePrevious = !this.overridePrevious))} checked={this.overridePrevious} /> <label>Override previous </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 + {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 + <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> + + {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> |