-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* repo share admin * check share_to param * [repo 'share admin' dialog] removed 'internal links'; fixup & improvements --------- Co-authored-by: llj <[email protected]>
- Loading branch information
Showing
14 changed files
with
1,244 additions
and
247 deletions.
There are no files selected for viewing
130 changes: 130 additions & 0 deletions
130
frontend/src/components/dialog/repo-share-admin-dialog.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import React, { Fragment } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap'; | ||
import { gettext, canGenerateShareLink, canGenerateUploadLink } from '../../utils/constants'; | ||
import RepoShareAdminShareLinks from './repo-share-admin/share-links'; | ||
import RepoShareAdminUploadLinks from './repo-share-admin/upload-links'; | ||
import RepoShareAdminUserShares from './repo-share-admin/user-shares'; | ||
import RepoShareAdminGroupShares from './repo-share-admin/group-shares'; | ||
|
||
const propTypes = { | ||
repo: PropTypes.object.isRequired, | ||
toggleDialog: PropTypes.func.isRequired, | ||
}; | ||
|
||
class RepoShareAdminDialog extends React.Component { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.enableShareLink = !this.props.repo.encrypted && canGenerateShareLink; | ||
this.enableUploadLink = !this.props.repo.encrypted && canGenerateUploadLink; | ||
this.state = { | ||
activeTab: this.getInitialActiveTab() | ||
}; | ||
} | ||
|
||
getInitialActiveTab = () => { | ||
if (this.enableShareLink) { | ||
return 'shareLink'; | ||
} else if (this.enableUploadLink) { | ||
return 'uploadLink'; | ||
} else { | ||
return 'shareToUser'; | ||
} | ||
} | ||
|
||
toggle = (tab) => { | ||
if (this.state.activeTab !== tab) { | ||
this.setState({ activeTab: tab }); | ||
} | ||
} | ||
|
||
onTabKeyDown = (e) => { | ||
if (e.key == 'Enter' || e.key == 'Space') { | ||
e.target.click(); | ||
} | ||
} | ||
|
||
render() { | ||
const { activeTab } = this.state; | ||
const { repoName } = this.props.repo; | ||
|
||
return ( | ||
<div> | ||
<Modal isOpen={true} style={{maxWidth: '760px'}} className="share-dialog" toggle={this.props.toggleDialog}> | ||
<ModalHeader toggle={this.props.toggleDialog}> | ||
<span className="op-target" title={repoName}>{repoName}</span> {gettext('Share Admin')} | ||
</ModalHeader> | ||
<ModalBody className="dialog-list-container share-dialog-content" role="tablist"> | ||
<Fragment> | ||
<div className="share-dialog-side"> | ||
<Nav pills> | ||
{this.enableShareLink && | ||
<NavItem role="tab" aria-selected={activeTab === 'shareLink'} aria-controls="share-link-panel"> | ||
<NavLink className={activeTab === 'shareLink' ? 'active' : ''} onClick={(this.toggle.bind(this, 'shareLink'))} tabIndex="0" onKeyDown={this.onTabKeyDown}> | ||
{gettext('Share Links')} | ||
</NavLink> | ||
</NavItem> | ||
} | ||
{this.enableUploadLink && | ||
<NavItem role="tab" aria-selected={activeTab === 'uploadLink'} aria-controls="upload-link-panel"> | ||
<NavLink className={activeTab === 'uploadLink' ? 'active' : ''} onClick={this.toggle.bind(this, 'uploadLink')} tabIndex="0" onKeyDown={this.onTabKeyDown}> | ||
{gettext('Upload Links')} | ||
</NavLink> | ||
</NavItem> | ||
} | ||
<NavItem role="tab" aria-selected={activeTab === 'shareToUser'} aria-controls="share-to-user-panel"> | ||
<NavLink className={activeTab === 'shareToUser' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToUser')} tabIndex="0" onKeyDown={this.onTabKeyDown}> | ||
{gettext('User Shares')} | ||
</NavLink> | ||
</NavItem> | ||
<NavItem role="tab" aria-selected={activeTab === 'shareToGroup'} aria-controls="share-to-group-panel"> | ||
<NavLink className={activeTab === 'shareToGroup' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToGroup')} tabIndex="0" onKeyDown={this.onTabKeyDown}> | ||
{gettext('Group Shares')} | ||
</NavLink> | ||
</NavItem> | ||
</Nav> | ||
</div> | ||
<div className="share-dialog-main"> | ||
<TabContent activeTab={this.state.activeTab}> | ||
{(this.enableShareLink && activeTab === 'shareLink') && | ||
<TabPane tabId="shareLink" role="tabpanel" id="share-link-panel"> | ||
<RepoShareAdminShareLinks | ||
repo={this.props.repo} | ||
/> | ||
</TabPane> | ||
} | ||
{(this.enableUploadLink && activeTab === 'uploadLink') && | ||
<TabPane tabId="uploadLink" role="tabpanel" id="upload-link-panel"> | ||
<RepoShareAdminUploadLinks | ||
repo={this.props.repo} | ||
/> | ||
</TabPane> | ||
} | ||
{activeTab === 'shareToUser' && | ||
<TabPane tabId="shareToUser" role="tabpanel" id="share-to-user-panel"> | ||
<RepoShareAdminUserShares | ||
repo={this.props.repo} | ||
/> | ||
</TabPane> | ||
} | ||
{activeTab === 'shareToGroup' && | ||
<TabPane tabId="shareToGroup" role="tabpanel" id="share-to-group-panel"> | ||
<RepoShareAdminGroupShares | ||
repo={this.props.repo} | ||
/> | ||
</TabPane> | ||
} | ||
</TabContent> | ||
</div> | ||
</Fragment> | ||
</ModalBody> | ||
</Modal> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
RepoShareAdminDialog.propTypes = propTypes; | ||
|
||
export default RepoShareAdminDialog; |
213 changes: 213 additions & 0 deletions
213
frontend/src/components/dialog/repo-share-admin/group-shares.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import React, { Component, Fragment } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Link } from '@gatsbyjs/reach-router'; | ||
import { Utils } from '../../../utils/utils'; | ||
import { seafileAPI } from '../../../utils/seafile-api'; | ||
import { gettext, siteRoot, isPro, username } from '../../../utils/constants'; | ||
import Loading from '../../../components/loading'; | ||
import toaster from '../../../components/toast'; | ||
import EmptyTip from '../../../components/empty-tip'; | ||
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor'; | ||
|
||
const itemPropTypes = { | ||
item: PropTypes.object.isRequired, | ||
deleteItem: PropTypes.func.isRequired, | ||
isRepoOwner: PropTypes.bool.isRequired | ||
}; | ||
|
||
class Item extends Component { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
permission: this.props.item.permission, | ||
isOperationShow: false, | ||
isShowPermEditor: false, | ||
}; | ||
this.permissions = ['rw', 'r']; | ||
if (isPro) { | ||
if (this.props.item.path === '/' && this.props.isRepoOwner) { | ||
this.permissions.push('admin'); | ||
} | ||
this.permissions.push('cloud-edit', 'preview'); | ||
} | ||
} | ||
|
||
onMouseEnter = () => { | ||
this.setState({isOperationShow: true}); | ||
}; | ||
|
||
onMouseLeave = () => { | ||
this.setState({isOperationShow: false}); | ||
}; | ||
|
||
onDeleteLink = (e) => { | ||
e.preventDefault(); | ||
this.props.deleteItem(this.props.item); | ||
}; | ||
|
||
changePerm = (permission) => { | ||
const item = this.props.item; | ||
seafileAPI.updateShareToGroupItemPermission(item.repo_id, item.path, 'group', item.share_to, permission).then(() => { | ||
this.setState({ | ||
permission: permission, | ||
}); | ||
}).catch(error => { | ||
let errMessage = Utils.getErrorMsg(error); | ||
toaster.danger(errMessage); | ||
}); | ||
} | ||
|
||
onEditPermission = (event) => { | ||
event.nativeEvent.stopImmediatePropagation(); | ||
this.setState({isShowPermEditor: true}); | ||
} | ||
|
||
render() { | ||
|
||
let objUrl; | ||
let item = this.props.item; | ||
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1); | ||
|
||
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`; | ||
|
||
return ( | ||
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}> | ||
<td> | ||
<Link to={objUrl}>{Utils.getFolderName(item.path)}</Link> | ||
</td> | ||
<td className="name">{item.share_to_name}</td> | ||
<td> | ||
{!this.state.isShowPermEditor && ( | ||
<div> | ||
<span>{item.permission_name || Utils.sharePerms(this.state.permission)}</span> | ||
{this.state.isOperationShow && ( | ||
<a href="#" | ||
role="button" | ||
aria-label={gettext('Edit')} | ||
title={gettext('Edit')} | ||
className="fa fa-pencil-alt attr-action-icon" | ||
onClick={this.onEditPermission}> | ||
</a> | ||
)} | ||
</div> | ||
)} | ||
{this.state.isShowPermEditor && ( | ||
<SharePermissionEditor | ||
repoID={item.repo_id} | ||
isTextMode={true} | ||
isEditIconShow={this.state.isOperationShow} | ||
isEditing={true} | ||
currentPermission={this.state.permission} | ||
permissions={this.permissions} | ||
onPermissionChanged={this.changePerm} | ||
/> | ||
)} | ||
</td> | ||
<td> | ||
<span | ||
tabIndex="0" | ||
role="button" | ||
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'invisible'}`} | ||
onClick={this.onDeleteLink} | ||
onKeyDown={Utils.onKeyDown} | ||
title={gettext('Delete')} | ||
aria-label={gettext('Delete')} | ||
> | ||
</span> | ||
</td> | ||
</tr> | ||
); | ||
} | ||
} | ||
|
||
Item.propTypes = itemPropTypes; | ||
|
||
const propTypes = { | ||
repo: PropTypes.object.isRequired, | ||
}; | ||
|
||
class RepoShareAdminGroupShares extends Component { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
loading: true, | ||
errorMsg: '', | ||
items: [], | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
seafileAPI.getAllRepoFolderShareInfo(this.props.repo.repo_id, 'group').then((res) => { | ||
this.setState({ | ||
loading: false, | ||
items: res.data.share_info_list, | ||
}); | ||
}).catch((error) => { | ||
this.setState({ | ||
loading: false, | ||
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403 | ||
}); | ||
}); | ||
} | ||
|
||
deleteItem = (item) => { | ||
seafileAPI.deleteShareToGroupItem(item.repo_id, item.path, 'group', item.share_to).then(res => { | ||
let items = this.state.items.filter(shareItem => { | ||
return shareItem.path + shareItem.share_to !== item.path + item.share_to; | ||
}); | ||
this.setState({items: items}); | ||
let message = gettext('Successfully deleted 1 item'); | ||
toaster.success(message); | ||
}).catch((error) => { | ||
let errMessage = Utils.getErrorMsg(error); | ||
toaster.danger(errMessage); | ||
}); | ||
} | ||
|
||
render() { | ||
const { loading, errorMsg, items } = this.state; | ||
const { repo } = this.props; | ||
const isRepoOwner = repo.owner_email === username; | ||
return ( | ||
<Fragment> | ||
{loading && <Loading />} | ||
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>} | ||
{!loading && !errorMsg && !items.length && | ||
<EmptyTip forDialog={true}> | ||
<p className="text-secondary">{gettext('No group shares')}</p> | ||
</EmptyTip> | ||
} | ||
{!loading && !errorMsg && items.length > 0 && | ||
<table className="table-hover"> | ||
<thead> | ||
<tr> | ||
<th width="30%">{gettext('Name')}</th> | ||
<th width="30%">{gettext('Group')}</th> | ||
<th width="30%">{gettext('Permission')}</th> | ||
<th width="10%"></th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{items.map((item, index) => { | ||
return ( | ||
<Item | ||
key={index} | ||
item={item} | ||
deleteItem={this.deleteItem} | ||
isRepoOwner={isRepoOwner} | ||
/> | ||
); | ||
})} | ||
</tbody> | ||
</table> | ||
} | ||
</Fragment> | ||
); | ||
} | ||
} | ||
|
||
RepoShareAdminGroupShares.propTypes = propTypes; | ||
|
||
export default RepoShareAdminGroupShares; |
Oops, something went wrong.