2018-12-29 10:25:18 +00:00
import React , { Component , Fragment } from 'react' ;
2019-07-17 09:37:39 +00:00
import { Dropdown , DropdownToggle , DropdownItem } from 'reactstrap' ;
2018-12-28 08:28:42 +00:00
import PropTypes from 'prop-types' ;
2018-11-22 08:49:48 +00:00
import moment from 'moment' ;
2019-03-19 02:35:21 +00:00
import cookie from 'react-cookies' ;
2018-11-30 09:18:41 +00:00
import { Link } from '@reach/router' ;
2019-12-05 07:45:16 +00:00
import { gettext , siteRoot , isPro } from '../../utils/constants' ;
2018-11-22 08:49:48 +00:00
import { seafileAPI } from '../../utils/seafile-api' ;
import { Utils } from '../../utils/utils' ;
2019-07-16 02:01:09 +00:00
import toaster from '../../components/toast' ;
2018-12-28 08:28:42 +00:00
import Repo from '../../models/repo' ;
2018-11-22 08:49:48 +00:00
import Loading from '../../components/loading' ;
2019-06-10 09:30:10 +00:00
import EmptyTip from '../../components/empty-tip' ;
2019-07-18 12:21:50 +00:00
import LibsMobileThead from '../../components/libs-mobile-thead' ;
2018-12-29 10:25:18 +00:00
import ModalPotal from '../../components/modal-portal' ;
import ShareDialog from '../../components/dialog/share-dialog' ;
2019-07-17 09:37:39 +00:00
import SortOptionsDialog from '../../components/dialog/sort-options' ;
2018-11-22 08:49:48 +00:00
class Content extends Component {
2018-12-28 08:28:42 +00:00
sortByName = ( e ) => {
e . preventDefault ( ) ;
const sortBy = 'name' ;
const sortOrder = this . props . sortOrder == 'asc' ? 'desc' : 'asc' ;
this . props . sortItems ( sortBy , sortOrder ) ;
}
sortByTime = ( e ) => {
e . preventDefault ( ) ;
const sortBy = 'time' ;
const sortOrder = this . props . sortOrder == 'asc' ? 'desc' : 'asc' ;
this . props . sortItems ( sortBy , sortOrder ) ;
}
2019-05-29 05:57:12 +00:00
sortBySize = ( e ) => {
e . preventDefault ( ) ;
const sortBy = 'size' ;
const sortOrder = this . props . sortOrder == 'asc' ? 'desc' : 'asc' ;
this . props . sortItems ( sortBy , sortOrder ) ;
}
2018-11-22 08:49:48 +00:00
render ( ) {
2018-12-28 08:28:42 +00:00
const { loading , errorMsg , items , sortBy , sortOrder } = this . props ;
2019-07-22 09:09:10 +00:00
2019-01-05 08:47:20 +00:00
const emptyTip = (
2019-06-10 09:30:10 +00:00
< EmptyTip >
2020-01-03 08:50:17 +00:00
< h2 > { gettext ( 'No shared libraries' ) } < / h 2 >
< p > { gettext ( 'No libraries have been shared directly with you. A shared library can be shared with full or restricted permission. If you need access to a library owned by another user, ask the user to share the library with you.' ) } < / p >
2019-06-10 09:30:10 +00:00
< / E m p t y T i p >
2019-01-05 08:47:20 +00:00
) ;
2018-11-22 08:49:48 +00:00
if ( loading ) {
return < Loading / > ;
} else if ( errorMsg ) {
return < p className = "error text-center" > { errorMsg } < / p > ;
} else {
2018-12-28 08:28:42 +00:00
// sort
const sortByName = sortBy == 'name' ;
const sortByTime = sortBy == 'time' ;
2019-05-29 05:57:12 +00:00
const sortBySize = sortBy == 'size' ;
2018-12-28 08:28:42 +00:00
const sortIcon = sortOrder == 'asc' ? < span className = "fas fa-caret-up" > < / s p a n > : < s p a n c l a s s N a m e = " f a s f a - c a r e t - d o w n " > < / s p a n > ;
2018-11-22 08:49:48 +00:00
const desktopThead = (
< thead >
< tr >
2019-02-26 06:14:00 +00:00
< th width = "4%" > < / t h >
2019-01-31 09:37:02 +00:00
< th width = "4%" > < span className = "sr-only" > { gettext ( 'Library Type' ) } < / s p a n > < / t h >
2019-02-26 06:14:00 +00:00
< th width = "34%" > < a className = "d-block table-sort-op" href = "#" onClick = { this . sortByName } > { gettext ( 'Name' ) } { sortByName && sortIcon } < / a > < / t h >
2019-01-31 09:37:02 +00:00
< th width = "10%" > < span className = "sr-only" > { gettext ( 'Actions' ) } < / s p a n > < / t h >
2019-05-29 05:57:12 +00:00
< th width = "14%" > < a className = "d-block table-sort-op" href = "#" onClick = { this . sortBySize } > { gettext ( 'Size' ) } { sortBySize && sortIcon } < / a > < / t h >
2018-12-28 08:28:42 +00:00
< th width = "18%" > < a className = "d-block table-sort-op" href = "#" onClick = { this . sortByTime } > { gettext ( 'Last Update' ) } { sortByTime && sortIcon } < / a > < / t h >
2019-01-31 09:37:02 +00:00
< th width = "16%" > { gettext ( 'Owner' ) } < / t h >
2018-11-22 08:49:48 +00:00
< / t r >
< / t h e a d >
) ;
2019-08-22 12:23:08 +00:00
const isDesktop = Utils . isDesktop ( ) ;
2018-11-22 08:49:48 +00:00
const table = (
2019-08-22 12:23:08 +00:00
< table className = { isDesktop ? '' : 'table-thead-hidden' } >
{ isDesktop ? desktopThead : < LibsMobileThead / > }
< tbody >
{ items . map ( ( item , index ) => {
return < Item key = { index } data = { item } isDesktop = { isDesktop } / > ;
} ) }
< / t b o d y >
2018-11-22 08:49:48 +00:00
< / t a b l e >
) ;
2019-07-22 09:09:10 +00:00
return items . length ? table : emptyTip ;
2018-11-22 08:49:48 +00:00
}
}
}
2018-12-28 08:28:42 +00:00
Content . propTypes = {
loading : PropTypes . bool . isRequired ,
errorMsg : PropTypes . string . isRequired ,
items : PropTypes . array . isRequired ,
sortBy : PropTypes . string . isRequired ,
sortOrder : PropTypes . string . isRequired ,
sortItems : PropTypes . func . isRequired
} ;
2018-11-22 08:49:48 +00:00
class Item extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
showOpIcon : false ,
2018-12-29 10:25:18 +00:00
unshared : false ,
isShowSharedDialog : false ,
2019-02-26 06:14:00 +00:00
isStarred : this . props . data . starred ,
2019-07-17 09:37:39 +00:00
isOpMenuOpen : false // for mobile
2018-11-22 08:49:48 +00:00
} ;
2019-07-17 09:37:39 +00:00
}
2018-11-22 08:49:48 +00:00
2019-07-17 09:37:39 +00:00
toggleOpMenu = ( ) => {
this . setState ( {
isOpMenuOpen : ! this . state . isOpMenuOpen
} ) ;
2018-11-22 08:49:48 +00:00
}
2019-07-17 09:37:39 +00:00
handleMouseOver = ( ) => {
2018-11-22 08:49:48 +00:00
this . setState ( {
showOpIcon : true
} ) ;
}
2019-07-17 09:37:39 +00:00
handleMouseOut = ( ) => {
2018-11-22 08:49:48 +00:00
this . setState ( {
showOpIcon : false
} ) ;
}
2019-07-17 09:37:39 +00:00
share = ( e ) => {
2018-11-22 08:49:48 +00:00
e . preventDefault ( ) ;
2018-12-29 10:25:18 +00:00
this . setState ( { isShowSharedDialog : true } ) ;
2018-11-22 08:49:48 +00:00
}
2019-07-17 09:37:39 +00:00
leaveShare = ( e ) => {
2018-11-22 08:49:48 +00:00
e . preventDefault ( ) ;
const data = this . props . data ;
let request ;
if ( data . owner _email . indexOf ( '@seafile_group' ) == - 1 ) {
let options = {
'share_type' : 'personal' ,
'from' : data . owner _email
} ;
request = seafileAPI . leaveShareRepo ( data . repo _id , options ) ;
} else {
request = seafileAPI . leaveShareGroupOwnedRepo ( data . repo _id ) ;
}
request . then ( ( res ) => {
2019-07-16 02:01:09 +00:00
this . setState ( { unshared : true } ) ;
let message = gettext ( 'Successfully unshared {name}' ) . replace ( '{name}' , data . repo _name ) ;
toaster . success ( message ) ;
} ) . catch ( error => {
let errMessage = Utils . getErrorMsg ( error ) ;
if ( errMessage === gettext ( 'Error' ) ) {
2019-07-24 23:55:52 +00:00
errMessage = gettext ( 'Failed to unshare {name}' ) . replace ( '{name}' , data . repo _name ) ;
2019-07-16 02:01:09 +00:00
}
toaster ( errMessage ) ;
2018-11-22 08:49:48 +00:00
} ) ;
}
2018-12-29 10:25:18 +00:00
toggleShareDialog = ( ) => {
this . setState ( { isShowSharedDialog : false } ) ;
}
2019-02-26 06:14:00 +00:00
onStarRepo = ( ) => {
2019-07-17 09:37:39 +00:00
const repoName = this . props . data . repo _name ;
2019-02-26 06:14:00 +00:00
if ( this . state . isStarred ) {
2019-03-27 03:28:00 +00:00
seafileAPI . unstarItem ( this . props . data . repo _id , '/' ) . then ( ( ) => {
2019-02-26 06:14:00 +00:00
this . setState ( { isStarred : ! this . state . isStarred } ) ;
2019-08-22 12:23:08 +00:00
const msg = gettext ( 'Successfully unstarred {library_name_placeholder}.' )
. replace ( '{library_name_placeholder}' , repoName ) ;
toaster . success ( msg ) ;
2019-07-16 02:01:09 +00:00
} ) . catch ( error => {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
2019-02-26 06:14:00 +00:00
} ) ;
} else {
seafileAPI . starItem ( this . props . data . repo _id , '/' ) . then ( ( ) => {
this . setState ( { isStarred : ! this . state . isStarred } ) ;
2019-08-22 12:23:08 +00:00
const msg = gettext ( 'Successfully starred {library_name_placeholder}.' )
. replace ( '{library_name_placeholder}' , repoName ) ;
toaster . success ( msg ) ;
2019-07-16 02:01:09 +00:00
} ) . catch ( error => {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
2019-06-04 09:59:54 +00:00
} ) ;
2019-02-26 06:14:00 +00:00
}
}
2018-11-22 08:49:48 +00:00
render ( ) {
if ( this . state . unshared ) {
return null ;
}
const data = this . props . data ;
2019-07-22 09:09:10 +00:00
data . icon _url = Utils . getLibIconUrl ( data ) ;
2019-01-29 07:22:02 +00:00
data . icon _title = Utils . getLibIconTitle ( data ) ;
2018-11-22 08:49:48 +00:00
data . url = ` ${ siteRoot } #shared-libs/lib/ ${ data . repo _id } / ` ;
let iconVisibility = this . state . showOpIcon ? '' : ' invisible' ;
2019-07-22 09:09:10 +00:00
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + iconVisibility ;
2018-12-28 08:34:04 +00:00
let leaveShareIconClassName = 'op-icon sf2-icon-x3' + iconVisibility ;
2019-10-15 02:37:06 +00:00
let shareRepoUrl = ` ${ siteRoot } library/ ${ data . repo _id } / ${ Utils . encodePath ( data . repo _name ) } / ` ;
2018-11-22 08:49:48 +00:00
const desktopItem = (
2018-12-29 10:25:18 +00:00
< Fragment >
< tr onMouseOver = { this . handleMouseOver } onMouseOut = { this . handleMouseOut } >
2019-02-26 06:14:00 +00:00
< td className = "text-center" >
{ ! this . state . isStarred && < i className = "far fa-star star-empty cursor-pointer" onClick = { this . onStarRepo } > < / i > }
{ this . state . isStarred && < i className = "fas fa-star cursor-pointer" onClick = { this . onStarRepo } > < / i > }
< / t d >
2019-01-11 07:37:02 +00:00
< td > < img src = { data . icon _url } title = { data . icon _title } alt = { data . icon _title } width = "24" / > < / t d >
2019-10-15 02:37:06 +00:00
< td > < Link to = { shareRepoUrl } > { data . repo _name } < / L i n k > < / t d >
2018-12-29 10:25:18 +00:00
< td >
{ ( isPro && data . is _admin ) &&
2019-01-31 09:37:02 +00:00
< a href = "#" className = { shareIconClassName } title = { gettext ( 'Share' ) } onClick = { this . share } > < / a >
2018-12-29 10:25:18 +00:00
}
2019-01-31 09:37:02 +00:00
< a href = "#" className = { leaveShareIconClassName } title = { gettext ( 'Leave Share' ) } onClick = { this . leaveShare } > < / a >
2018-12-29 10:25:18 +00:00
< / t d >
< td > { data . size } < / t d >
< td title = { moment ( data . last _modified ) . format ( 'llll' ) } > { moment ( data . last _modified ) . fromNow ( ) } < / t d >
< td title = { data . owner _contact _email } > { data . owner _name } < / t d >
< / t r >
{ this . state . isShowSharedDialog && (
< ModalPotal >
2019-07-22 09:09:10 +00:00
< ShareDialog
2018-12-29 10:25:18 +00:00
itemType = { 'library' }
itemName = { data . repo _name }
itemPath = { '/' }
repoID = { data . repo _id }
2019-01-29 02:06:26 +00:00
repoEncrypted = { data . encrypted }
enableDirPrivateShare = { true }
userPerm = { data . permission }
isAdmin = { true }
2018-12-29 10:25:18 +00:00
toggleDialog = { this . toggleShareDialog }
/ >
< / M o d a l P o t a l >
) }
< / F r a g m e n t >
2018-11-22 08:49:48 +00:00
) ;
const mobileItem = (
2018-12-29 10:25:18 +00:00
< Fragment >
< tr onMouseOver = { this . handleMouseOver } onMouseOut = { this . handleMouseOut } >
2019-01-11 07:37:02 +00:00
< td > < img src = { data . icon _url } title = { data . icon _title } alt = { data . icon _title } width = "24" / > < / t d >
2018-12-29 10:25:18 +00:00
< td >
2019-10-15 02:37:06 +00:00
< Link to = { shareRepoUrl } > { data . repo _name } < /Link><br / >
2018-12-29 10:25:18 +00:00
< span className = "item-meta-info" title = { data . owner _contact _email } > { data . owner _name } < / s p a n >
< span className = "item-meta-info" > { data . size } < / s p a n >
< span className = "item-meta-info" title = { moment ( data . last _modified ) . format ( 'llll' ) } > { moment ( data . last _modified ) . fromNow ( ) } < / s p a n >
< / t d >
< td >
2019-07-17 09:37:39 +00:00
< Dropdown isOpen = { this . state . isOpMenuOpen } toggle = { this . toggleOpMenu } >
< DropdownToggle
tag = "i"
className = "sf-dropdown-toggle fa fa-ellipsis-v ml-0"
title = { gettext ( 'More Operations' ) }
data - toggle = "dropdown"
aria - expanded = { this . state . isOpMenuOpen }
/ >
< div className = { this . state . isOpMenuOpen ? '' : 'd-none' } onClick = { this . toggleOpMenu } >
< div className = "mobile-operation-menu-bg-layer" > < / d i v >
< div className = "mobile-operation-menu" >
< DropdownItem className = "mobile-menu-item" onClick = { this . onStarRepo } > { this . state . isStarred ? gettext ( 'Unstar' ) : gettext ( 'Star' ) } < / D r o p d o w n I t e m >
{ ( isPro && data . is _admin ) &&
< DropdownItem className = "mobile-menu-item" onClick = { this . share } > { gettext ( 'Share' ) } < / D r o p d o w n I t e m >
}
< DropdownItem className = "mobile-menu-item" onClick = { this . leaveShare } > { gettext ( 'Leave Share' ) } < / D r o p d o w n I t e m >
< / d i v >
< / d i v >
< / D r o p d o w n >
2018-12-29 10:25:18 +00:00
< / t d >
< / t r >
2019-01-31 09:37:02 +00:00
{ this . state . isShowSharedDialog && (
< ModalPotal >
2019-07-22 09:09:10 +00:00
< ShareDialog
2019-01-31 09:37:02 +00:00
itemType = { 'library' }
itemName = { data . repo _name }
itemPath = { '/' }
repoID = { data . repo _id }
repoEncrypted = { data . encrypted }
enableDirPrivateShare = { true }
userPerm = { data . permission }
isAdmin = { true }
toggleDialog = { this . toggleShareDialog }
/ >
< / M o d a l P o t a l >
) }
2018-12-29 10:25:18 +00:00
< / F r a g m e n t >
2018-11-22 08:49:48 +00:00
) ;
2019-08-22 12:23:08 +00:00
return this . props . isDesktop ? desktopItem : mobileItem ;
2018-11-22 08:49:48 +00:00
}
}
2018-12-28 08:28:42 +00:00
Item . propTypes = {
2019-08-22 12:23:08 +00:00
isDesktop : PropTypes . bool . isRequired ,
2018-12-28 08:28:42 +00:00
data : PropTypes . object . isRequired
} ;
2018-11-22 08:49:48 +00:00
class SharedLibraries extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
loading : true ,
errorMsg : '' ,
2018-12-28 08:28:42 +00:00
items : [ ] ,
2019-05-29 05:57:12 +00:00
sortBy : cookie . load ( 'seafile-repo-dir-sort-by' ) || 'name' , // 'name' or 'time' or 'size'
2019-04-12 06:30:08 +00:00
sortOrder : cookie . load ( 'seafile-repo-dir-sort-order' ) || 'asc' , // 'asc' or 'desc'
2019-07-17 09:37:39 +00:00
isSortOptionsDialogOpen : false
2018-11-22 08:49:48 +00:00
} ;
}
componentDidMount ( ) {
seafileAPI . listRepos ( { type : 'shared' } ) . then ( ( res ) => {
2018-12-28 08:28:42 +00:00
let repoList = res . data . repos . map ( ( item ) => {
return new Repo ( item ) ;
} ) ;
2018-11-22 08:49:48 +00:00
this . setState ( {
loading : false ,
2018-12-28 08:28:42 +00:00
items : Utils . sortRepos ( repoList , this . state . sortBy , this . state . sortOrder )
2018-11-22 08:49:48 +00:00
} ) ;
} ) . catch ( ( error ) => {
2019-12-05 07:45:16 +00:00
this . setState ( {
loading : false ,
errorMsg : Utils . getErrorMsg ( error , true ) // true: show login tip if 403
} ) ;
2018-11-22 08:49:48 +00:00
} ) ;
}
2018-12-28 08:28:42 +00:00
sortItems = ( sortBy , sortOrder ) => {
2019-04-12 06:30:08 +00:00
cookie . save ( 'seafile-repo-dir-sort-by' , sortBy ) ;
cookie . save ( 'seafile-repo-dir-sort-order' , sortOrder ) ;
2018-12-28 08:28:42 +00:00
this . setState ( {
sortBy : sortBy ,
sortOrder : sortOrder ,
items : Utils . sortRepos ( this . state . items , sortBy , sortOrder )
} ) ;
}
2019-07-17 09:37:39 +00:00
toggleSortOptionsDialog = ( ) => {
this . setState ( {
isSortOptionsDialogOpen : ! this . state . isSortOptionsDialogOpen
} ) ;
}
2018-11-22 08:49:48 +00:00
render ( ) {
return (
2019-07-17 09:37:39 +00:00
< Fragment >
< div className = "main-panel-center" >
< div className = "cur-view-container" >
2019-08-29 07:28:54 +00:00
< div className = "cur-view-path" >
2019-07-17 09:37:39 +00:00
< h3 className = "sf-heading m-0" > { gettext ( 'Shared with me' ) } < / h 3 >
2019-08-22 12:23:08 +00:00
{ ( ! Utils . isDesktop ( ) && this . state . items . length > 0 ) && < span className = "sf3-font sf3-font-sort action-icon" onClick = { this . toggleSortOptionsDialog } > < / s p a n > }
2019-07-17 09:37:39 +00:00
< / d i v >
< div className = "cur-view-content" >
< Content
loading = { this . state . loading }
errorMsg = { this . state . errorMsg }
items = { this . state . items }
sortBy = { this . state . sortBy }
sortOrder = { this . state . sortOrder }
sortItems = { this . sortItems }
/ >
< / d i v >
2018-11-26 06:00:32 +00:00
< / d i v >
2018-11-22 08:49:48 +00:00
< / d i v >
2019-07-17 09:37:39 +00:00
{ this . state . isSortOptionsDialogOpen &&
< SortOptionsDialog
toggleDialog = { this . toggleSortOptionsDialog }
sortBy = { this . state . sortBy }
sortOrder = { this . state . sortOrder }
sortItems = { this . sortItems }
/ >
}
< / F r a g m e n t >
2018-11-22 08:49:48 +00:00
) ;
}
}
export default SharedLibraries ;