2018-11-30 17:18:41 +08:00
import React , { Component , Fragment } from 'react' ;
import moment from 'moment' ;
import { Link } from '@reach/router' ;
import { Dropdown , DropdownMenu , DropdownToggle , DropdownItem } from 'reactstrap' ;
import { seafileAPI } from '../../utils/seafile-api' ;
import { Utils } from '../../utils/utils' ;
import { gettext , siteRoot , loginUrl , isPro , storages , canGenerateShareLink , canGenerateUploadLink , folderPermEnabled , enableRepoSnapshotLabel } from '../../utils/constants' ;
import Loading from '../../components/loading' ;
import DeleteItemPopup from './popups/delete-item' ;
2018-12-03 16:21:09 +08:00
import CommonToolbar from '../../components/toolbar/common-toolbar' ;
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar' ;
2018-11-30 17:18:41 +08:00
class Content extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
deleteItemPopupOpen : false
} ;
this . toggleDeleteItemPopup = this . toggleDeleteItemPopup . bind ( this ) ;
this . showDeleteItemPopup = this . showDeleteItemPopup . bind ( this ) ;
this . operations = {
showDeleteItemPopup : this . showDeleteItemPopup
} ;
}
toggleDeleteItemPopup ( ) {
this . setState ( {
deleteItemPopupOpen : ! this . state . deleteItemPopupOpen
} ) ;
}
showDeleteItemPopup ( data ) {
this . toggleDeleteItemPopup ( ) ;
this . setState ( {
deleteItemPopupData : data
} ) ;
}
render ( ) {
const { loading , errorMsg , items } = this . props . data ;
if ( loading ) {
return < Loading / > ;
} else if ( errorMsg ) {
return < p className = "error text-center" > { errorMsg } < / p > ;
} else {
const emptyTip = (
< div className = "empty-tip" >
< h2 > { gettext ( 'You have not created any libraries' ) } < / h 2 >
< p > { gettext ( 'You can create a library to organize your files. For example, you can create one for each of your projects. Each library can be synchronized and shared separately.' ) } < / p >
< / d i v >
) ;
// TODO: test 'storage backend'
const showStorageBackend = storages . length > 0 ; // only for desktop
const desktopThead = (
< thead >
< tr >
2018-12-06 14:39:21 +08:00
< th width = "4%" > < span className = "sr-only" > { gettext ( "Library Type" ) } < / s p a n > < / t h >
2018-12-06 15:01:40 +08:00
< th width = "42%" > { gettext ( "Name" ) } < a className = "table-sort-op by-name" href = "#" > { /*TODO: sort*/ } < span className = "sort-icon icon-caret-down hide" > < / s p a n > < / a > < / t h >
2018-12-06 14:39:21 +08:00
< th width = "14%" > < span className = "sr-only" > { gettext ( "Actions" ) } < / s p a n > < / t h >
2018-11-30 17:18:41 +08:00
2018-12-06 14:39:21 +08:00
< th width = { showStorageBackend ? '15%' : '20%' } > { gettext ( "Size" ) } < / t h >
2018-11-30 17:18:41 +08:00
{ showStorageBackend ? < th width = "10%" > { gettext ( 'Storage backend' ) } < / t h > : n u l l }
2018-12-06 14:39:21 +08:00
< th width = { showStorageBackend ? '15%' : '20%' } > { gettext ( "Last Update" ) } < a className = "table-sort-op by-time" href = "#" > { /*TODO: sort*/ } < span className = "sort-icon icon-caret-up" > < / s p a n > < / a > < / t h >
2018-11-30 17:18:41 +08:00
< / t r >
< / t h e a d >
) ;
const mobileThead = (
< thead >
< tr >
< th width = "18%" > < span className = "sr-only" > { gettext ( "Library Type" ) } < / s p a n > < / t h >
< th width = "76%" >
{ gettext ( "Sort:" ) } { /* TODO: sort */ }
{ gettext ( "name" ) } < a className = "table-sort-op mobile-table-sort-op by-name" href = "#" > < span className = "sort-icon icon-caret-down hide" > < / s p a n > < / a >
{ gettext ( "last update" ) } < a className = "table-sort-op mobile-table-sort-op by-time" href = "#" > < span className = "sort-icon icon-caret-up" > < / s p a n > < / a >
< / t h >
< th width = "6%" > < span className = "sr-only" > { gettext ( "Actions" ) } < / s p a n > < / t h >
< / t r >
< / t h e a d >
) ;
const table = (
< table >
{ window . innerWidth >= 768 ? desktopThead : mobileThead }
< TableBody items = { items } operations = { this . operations } / >
< / t a b l e >
) ;
const nonEmpty = (
< React . Fragment >
{ table }
< DeleteItemPopup isOpen = { this . state . deleteItemPopupOpen }
toggle = { this . toggleDeleteItemPopup } data = { this . state . deleteItemPopupData } / >
< / R e a c t . F r a g m e n t >
) ;
return items . length ? nonEmpty : emptyTip ;
}
}
}
class TableBody extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
items : this . props . items
} ;
}
render ( ) {
let listItems = this . state . items . map ( function ( item , index ) {
return < Item key = { index } data = { item } operations = { this . props . operations } / > ;
} , this ) ;
return (
< tbody > { listItems } < / t b o d y >
) ;
}
}
class Item extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
showOpIcon : false ,
operationMenuOpen : false ,
deleted : false
} ;
this . handleMouseOver = this . handleMouseOver . bind ( this ) ;
this . handleMouseOut = this . handleMouseOut . bind ( this ) ;
this . toggleOperationMenu = this . toggleOperationMenu . bind ( this ) ;
this . clickOperationMenuToggle = this . clickOperationMenuToggle . bind ( this ) ;
this . share = this . share . bind ( this ) ;
this . showDeleteItemPopup = this . showDeleteItemPopup . bind ( this ) ;
this . deleteItem = this . deleteItem . bind ( this ) ;
this . rename = this . rename . bind ( this ) ;
this . transfer = this . transfer . bind ( this ) ;
this . historySetting = this . historySetting . bind ( this ) ;
this . changePassword = this . changePassword . bind ( this ) ;
this . showLinks = this . showLinks . bind ( this ) ;
this . folderPerm = this . folderPerm . bind ( this ) ;
this . showDetails = this . showDetails . bind ( this ) ;
this . label = this . label . bind ( this ) ;
}
handleMouseOver ( ) {
if ( this . state . operationMenuOpen ) {
return ;
}
this . setState ( {
showOpIcon : true
} ) ;
}
handleMouseOut ( ) {
if ( this . state . operationMenuOpen ) {
return ;
}
this . setState ( {
showOpIcon : false
} ) ;
}
toggleOperationMenu ( ) {
this . setState ( {
operationMenuOpen : ! this . state . operationMenuOpen
} ) ;
}
clickOperationMenuToggle ( e ) {
e . preventDefault ( ) ;
this . toggleOperationMenu ( ) ;
}
share ( e ) {
e . preventDefault ( ) ;
// TODO
}
showDeleteItemPopup ( e ) {
e . preventDefault ( ) ; // for `<a>`
const data = this . props . data ;
this . props . operations . showDeleteItemPopup ( {
repoName : data . repo _name ,
yesCallback : this . deleteItem ,
_this : this
} ) ;
}
deleteItem ( ) {
const data = this . props . data ;
seafileAPI . deleteRepo ( data . repo _id ) . then ( ( res ) => {
this . setState ( {
deleted : true
} ) ;
// TODO: show feedback msg
} ) . catch ( ( error ) => {
// TODO: show feedback msg
} ) ;
}
rename ( ) {
}
transfer ( ) {
}
historySetting ( ) {
}
changePassword ( ) {
}
showLinks ( ) {
}
folderPerm ( ) {
}
showDetails ( ) {
}
label ( ) {
}
render ( ) {
if ( this . state . deleted ) {
return null ;
}
const data = this . props . data ;
const permission = data . permission ;
let is _readonly = false ;
if ( permission == 'r' || permission == 'preview' ) {
is _readonly = true ;
}
data . icon _url = Utils . getLibIconUrl ( {
is _encrypted : data . encrypted ,
is _readonly : is _readonly ,
size : Utils . isHiDPI ( ) ? 48 : 24
} ) ;
data . icon _title = Utils . getLibIconTitle ( {
'encrypted' : data . encrypted ,
'is_admin' : data . is _admin ,
'permission' : permission
} ) ;
data . url = ` ${ siteRoot } #my-libs/lib/ ${ data . repo _id } / ` ;
let iconVisibility = this . state . showOpIcon ? '' : ' invisible' ;
let shareIconClassName = 'sf2-icon-share sf2-x op-icon' + iconVisibility ;
let deleteIconClassName = 'sf2-icon-delete sf2-x op-icon' + iconVisibility ;
let operationMenuToggleIconClassName = 'sf2-icon-caret-down item-operation-menu-toggle-icon op-icon' ;
if ( window . innerWidth >= 768 ) {
operationMenuToggleIconClassName += iconVisibility ;
}
const showShareLinks = ! data . encrypted && ( canGenerateShareLink || canGenerateUploadLink ) ;
const commonToggle = (
< DropdownToggle
tag = "a" href = "#" className = { operationMenuToggleIconClassName } title = { gettext ( 'More Operations' ) }
onClick = { this . clickOperationMenuToggle }
data - toggle = "dropdown" aria - expanded = { this . state . operationMenuOpen } >
< / D r o p d o w n T o g g l e >
) ;
const commonOperationsInMenu = (
< React . Fragment >
< DropdownItem onClick = { this . rename } > { gettext ( 'Rename' ) } < / D r o p d o w n I t e m >
< DropdownItem onClick = { this . transfer } > { gettext ( 'Transfer' ) } < / D r o p d o w n I t e m >
< DropdownItem onClick = { this . historySetting } > { gettext ( 'History Setting' ) } < / D r o p d o w n I t e m >
{ data . encrypted ? < DropdownItem onClick = { this . changePassword } > { gettext ( 'Change Password' ) } < / D r o p d o w n I t e m > : ' ' }
{ showShareLinks ? < DropdownItem onClick = { this . showLinks } > { gettext ( 'Share Links' ) } < / D r o p d o w n I t e m > : ' ' }
{ folderPermEnabled ? < DropdownItem onClick = { this . folderPerm } > { gettext ( 'Folder Permission' ) } < / D r o p d o w n I t e m > : ' ' }
< DropdownItem onClick = { this . showDetails } > { gettext ( 'Details' ) } < / D r o p d o w n I t e m >
< / R e a c t . F r a g m e n t >
) ;
const desktopOperations = (
< div >
< a href = "#" className = { shareIconClassName } title = { gettext ( "Share" ) } onClick = { this . share } > < / a >
< a href = "#" className = { deleteIconClassName } title = { gettext ( "Delete" ) } onClick = { this . showDeleteItemPopup } > < / a >
< Dropdown isOpen = { this . state . operationMenuOpen } toggle = { this . toggleOperationMenu } >
{ commonToggle }
< DropdownMenu >
{ commonOperationsInMenu }
{ enableRepoSnapshotLabel ? < DropdownItem onClick = { this . label } > { gettext ( 'Label current state' ) } < / D r o p d o w n I t e m > : ' ' }
< / D r o p d o w n M e n u >
< / D r o p d o w n >
< / d i v >
) ;
const mobileOperations = (
< Dropdown isOpen = { this . state . operationMenuOpen } toggle = { this . toggleOperationMenu } >
{ commonToggle }
< div className = { ` ${ this . state . operationMenuOpen ? '' : 'd-none' } ` } onClick = { this . toggleOperationMenu } >
< div className = "mobile-operation-menu-bg-layer" > < / d i v >
< div className = "mobile-operation-menu" >
< DropdownItem onClick = { this . share } > { gettext ( 'Share' ) } < / D r o p d o w n I t e m >
< DropdownItem onClick = { this . showDeleteItemPopup } > { gettext ( 'Delete' ) } < / D r o p d o w n I t e m >
{ commonOperationsInMenu }
< / d i v >
< / d i v >
< / D r o p d o w n >
) ;
const desktopItem = (
< tr onMouseOver = { this . handleMouseOver } onMouseOut = { this . handleMouseOut } >
< td > < img src = { data . icon _url } title = { data . icon _title } alt = { data . icon _title } width = "24" / > < / t d >
< td >
{ data . repo _name ?
< Link to = { ` ${ siteRoot } library/ ${ data . repo _id } / ${ data . repo _name } / ` } > { data . repo _name } < / L i n k > :
gettext ( 'Broken (please contact your administrator to fix this library)' ) }
< / t d >
< td > { data . repo _name ? desktopOperations : '' } < / t d >
< td > { Utils . formatSize ( { bytes : data . size } ) } < / t d >
{ storages . length ? < td > { data . storage _name } < / t d > : n u l l }
< td title = { moment ( data . last _modified ) . format ( 'llll' ) } > { moment ( data . last _modified ) . fromNow ( ) } < / t d >
< / t r >
) ;
const mobileItem = (
< tr onMouseOver = { this . handleMouseOver } onMouseOut = { this . handleMouseOut } >
< td > < img src = { data . icon _url } title = { data . icon _title } alt = { data . icon _title } width = "24" / > < / t d >
< td >
{ data . repo _name ?
< Link to = { ` ${ siteRoot } library/ ${ data . repo _id } / ${ data . repo _name } / ` } > { data . repo _name } < / L i n k > :
gettext ( 'Broken (please contact your administrator to fix this library)' ) }
< br / >
< span className = "item-meta-info" > { Utils . formatSize ( { bytes : 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 > { data . repo _name ? mobileOperations : '' } < / t d >
< / t r >
) ;
return window . innerWidth >= 768 ? desktopItem : mobileItem ;
}
}
class MyLibraries extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
loading : true ,
errorMsg : '' ,
items : [ ]
} ;
}
componentDidMount ( ) {
seafileAPI . listRepos ( { type : 'mine' } ) . then ( ( res ) => {
// res: {data: {...}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
this . setState ( {
loading : false ,
items : res . data . repos
} ) ;
} ) . catch ( ( error ) => {
if ( error . response ) {
if ( error . response . status == 403 ) {
this . setState ( {
loading : false ,
errorMsg : gettext ( "Permission denied" )
} ) ;
location . href = ` ${ loginUrl } ?next= ${ encodeURIComponent ( location . href ) } ` ;
} else {
this . setState ( {
loading : false ,
errorMsg : gettext ( "Error" )
} ) ;
}
} else {
this . setState ( {
loading : false ,
errorMsg : gettext ( "Please check the network." )
} ) ;
}
} ) ;
}
render ( ) {
return (
< Fragment >
2018-12-03 16:21:09 +08:00
< div className = "main-panel-north" >
< RepoViewToolbar onShowSidePanel = { this . props . onShowSidePanel } / >
< CommonToolbar onSearchedClick = { this . props . onSearchedClick } / >
< / d i v >
2018-11-30 17:18:41 +08:00
< div className = "main-panel-center" >
< div className = "cur-view-container" >
< div className = "cur-view-path" >
< h3 className = "sf-heading" > { gettext ( "My Libraries" ) } < / h 3 >
< / d i v >
< div className = "cur-view-content" >
< Content data = { this . state } / >
< / d i v >
< / d i v >
< / d i v >
< / F r a g m e n t >
) ;
}
}
export default MyLibraries ;