mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-28 03:10:45 +00:00
Files (#5881)
* [side nav] redesigned it: added a new item 'Files', and made 'My Libraries' and some other items as its sub nav items * ['Files'] added new page 'Files'(added 'My Libraries' to it) * ['Files'] added 'Shared with me' to it * ['Files'] added 'Shared with all' to it * ['Files'] added 'Shared with groups' to it (removed 'details' for 'department')
This commit is contained in:
parent
0a417c0a23
commit
4eab55fdf9
@ -22,12 +22,13 @@ import OCMViaWebdav from './pages/ocm-via-webdav/ocm-via-webdav';
|
||||
import OCMRepoDir from './pages/share-with-ocm/remote-dir-view';
|
||||
import MyLibraries from './pages/my-libs/my-libs';
|
||||
import MyLibDeleted from './pages/my-libs/my-libs-deleted';
|
||||
import PublicSharedView from './pages/shared-with-all/public-shared-view';
|
||||
import PublicSharedView from './pages/shared-with-all';
|
||||
import LibContentView from './pages/lib-content-view/lib-content-view';
|
||||
import Group from './pages/groups/group-view';
|
||||
import Groups from './pages/groups/groups-view';
|
||||
import InvitationsView from './pages/invitations/invitations-view';
|
||||
import Wikis from './pages/wikis/wikis';
|
||||
import Libraries from './pages/libraries';
|
||||
import MainContentWrapper from './components/main-content-wrapper';
|
||||
|
||||
import './css/layout.css';
|
||||
@ -201,10 +202,6 @@ class App extends Component {
|
||||
render() {
|
||||
let { currentTab, isSidePanelClosed } = this.state;
|
||||
|
||||
const home = canAddRepo ?
|
||||
<MyLibraries path={ siteRoot } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} /> :
|
||||
<SharedLibrariesWrapper path={ siteRoot } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SystemNotification />
|
||||
@ -212,7 +209,8 @@ class App extends Component {
|
||||
<SidePanel isSidePanelClosed={this.state.isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} tabItemClick={this.tabItemClick} />
|
||||
<MainPanel>
|
||||
<Router className="reach-router">
|
||||
{home}
|
||||
<Libraries path={ siteRoot } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<Libraries path={ siteRoot + 'libraries' } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<FilesActivitiesWrapper path={siteRoot + 'dashboard'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyFileActivitiesWrapper path={siteRoot + 'my-activities'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<StarredWrapper path={siteRoot + 'starred'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
|
@ -39,7 +39,8 @@ class CreateGroupDialog extends React.Component {
|
||||
if (name) {
|
||||
let that = this;
|
||||
seafileAPI.createGroup(name).then((res)=> {
|
||||
that.props.onCreateGroup();
|
||||
that.props.onCreateGroup(res.data);
|
||||
this.props.toggleDialog();
|
||||
}).catch((error) => {
|
||||
let errorMsg = Utils.getErrorMsg(error);
|
||||
this.setState({errorMsg: errorMsg});
|
||||
@ -63,8 +64,8 @@ class CreateGroupDialog extends React.Component {
|
||||
|
||||
render() {
|
||||
return(
|
||||
<Modal isOpen={this.props.showAddGroupModal} toggle={this.props.toggleAddGroupModal} autoFocus={false}>
|
||||
<ModalHeader toggle={this.props.toggleAddGroupModal}>{gettext('New Group')}</ModalHeader>
|
||||
<Modal isOpen={true} toggle={this.props.toggleDialog} autoFocus={false}>
|
||||
<ModalHeader toggle={this.props.toggleDialog}>{gettext('New Group')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<label htmlFor="groupName">{gettext('Name')}</label>
|
||||
<Input
|
||||
@ -78,7 +79,7 @@ class CreateGroupDialog extends React.Component {
|
||||
<span className="error">{this.state.errorMsg}</span>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggleAddGroupModal}>{gettext('Cancel')}</Button>
|
||||
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmitGroup} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
@ -87,9 +88,8 @@ class CreateGroupDialog extends React.Component {
|
||||
}
|
||||
|
||||
const CreateGroupDialogPropTypes = {
|
||||
toggleAddGroupModal: PropTypes.func.isRequired,
|
||||
onCreateGroup: PropTypes.func.isRequired,
|
||||
showAddGroupModal: PropTypes.bool.isRequired,
|
||||
toggleDialog: PropTypes.func.isRequired,
|
||||
onCreateGroup: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
CreateGroupDialog.propTypes = CreateGroupDialogPropTypes;
|
||||
|
@ -18,6 +18,7 @@ class MainSideNav extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
FilesNavUnfolded: false,
|
||||
groupsExtended: false,
|
||||
sharedExtended: false,
|
||||
closeSideBar:false,
|
||||
@ -172,64 +173,71 @@ class MainSideNav extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
toggleFilesNav = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
FilesNavUnfolded: !this.state.FilesNavUnfolded
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let showActivity = isDocs || isPro || !isDBSqlite3;
|
||||
const { FilesNavUnfolded } = this.state;
|
||||
return (
|
||||
<div className="side-nav">
|
||||
<div className="side-nav-con">
|
||||
<h3 className="sf-heading">{gettext('Files')}</h3>
|
||||
<ul className="nav nav-pills flex-column nav-container">
|
||||
{canAddRepo && (
|
||||
<li className="nav-item">
|
||||
<Link to={ siteRoot + 'my-libs/' } className={`nav-link ellipsis ${this.getActiveClass('my-libs') || this.getActiveClass('deleted') }`} title={gettext('My Libraries')} onClick={(e) => this.tabItemClick(e, 'my-libs')}>
|
||||
<span className="sf2-icon-user" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('My Libraries')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'shared-libs/'} className={`nav-link ellipsis ${this.getActiveClass('shared-libs')}`} title={gettext('Shared with me')} onClick={(e) => this.tabItemClick(e, 'shared-libs')}>
|
||||
<span className="sf2-icon-share" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared with me')}</span>
|
||||
<li className="nav-item flex-column" id="files">
|
||||
<Link to={ siteRoot + 'libraries/' } className={`nav-link ellipsis ${this.getActiveClass('libraries')}`} title={gettext('Files')} onClick={(e) => this.tabItemClick(e, 'libraries')}>
|
||||
<span className="sf3-font-files sf3-font" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Files')}</span>
|
||||
<span className={`toggle-icon fas ${FilesNavUnfolded ? 'fa-caret-down':'fa-caret-left'} ${this.getActiveClass('libraries') ? 'text-white' : ''}`} aria-hidden="true" onClick={this.toggleFilesNav}></span>
|
||||
</Link>
|
||||
<ul id="files-sub-nav" className={`nav sub-nav nav-pills flex-column ${FilesNavUnfolded ? 'side-panel-slide' : 'side-panel-slide-up'}`}>
|
||||
{canAddRepo && (
|
||||
<li className="nav-item">
|
||||
<Link to={ siteRoot + 'my-libs/' } className={`nav-link ellipsis ${this.getActiveClass('my-libs') || this.getActiveClass('deleted') }`} title={gettext('My Libraries')} onClick={(e) => this.tabItemClick(e, 'my-libs')}>
|
||||
<span className="nav-text">{gettext('My Libraries')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'shared-libs/'} className={`nav-link ellipsis ${this.getActiveClass('shared-libs')}`} title={gettext('Shared with me')} onClick={(e) => this.tabItemClick(e, 'shared-libs')}>
|
||||
<span className="nav-text">{gettext('Shared with me')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
{canViewOrg &&
|
||||
<li className="nav-item" onClick={(e) => this.tabItemClick(e, 'org')}>
|
||||
<Link to={ siteRoot + 'org/' } className={`nav-link ellipsis ${this.getActiveClass('org')}`} title={gettext('Shared with all')}>
|
||||
<span className="nav-text">{gettext('Shared with all')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
<li className="nav-item flex-column" id="group-nav">
|
||||
<a className="nav-link ellipsis d-flex pr-1" title={gettext('Shared with groups')} onClick={this.grpsExtend}>
|
||||
<span className="nav-text">{gettext('Shared with groups')}</span>
|
||||
<span className={`toggle-icon fas ${this.state.groupsExtended ?'fa-caret-down':'fa-caret-left'}`} aria-hidden="true"></span>
|
||||
</a>
|
||||
{this.renderSharedGroups()}
|
||||
</li>
|
||||
{enableOCM &&
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className={`nav-link ellipsis ${this.getActiveClass('shared-with-ocm')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'shared-with-ocm')}>
|
||||
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
{enableOCMViaWebdav &&
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'ocm-via-webdav/'} className={`nav-link ellipsis ${this.getActiveClass('ocm-via-webdav')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'ocm-via-webdav')}>
|
||||
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</li>
|
||||
{ canViewOrg &&
|
||||
<li className="nav-item" onClick={(e) => this.tabItemClick(e, 'org')}>
|
||||
<Link to={ siteRoot + 'org/' } className={`nav-link ellipsis ${this.getActiveClass('org')}`} title={gettext('Shared with all')}>
|
||||
<span className="sf2-icon-organization" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared with all')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
<li className="nav-item flex-column" id="group-nav">
|
||||
<a className="nav-link ellipsis" title={gettext('Shared with groups')} onClick={this.grpsExtend}>
|
||||
<span className="sf2-icon-group" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared with groups')}</span>
|
||||
<span className={`toggle-icon fas ${this.state.groupsExtended ?'fa-caret-down':'fa-caret-left'}`} aria-hidden="true"></span>
|
||||
</a>
|
||||
{this.renderSharedGroups()}
|
||||
</li>
|
||||
{enableOCM &&
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className={`nav-link ellipsis ${this.getActiveClass('shared-with-ocm')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'shared-with-ocm')}>
|
||||
<span className="sf3-font-share-from-other-servers sf3-font" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
{enableOCMViaWebdav &&
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'ocm-via-webdav/'} className={`nav-link ellipsis ${this.getActiveClass('ocm-via-webdav')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'ocm-via-webdav')}>
|
||||
<span className="sf3-font-share-from-other-servers sf3-font" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
|
||||
<h3 className="sf-heading">{gettext('Tools')}</h3>
|
||||
<ul className="nav nav-pills flex-column nav-container">
|
||||
<li className="nav-item">
|
||||
<Link className={`nav-link ellipsis ${this.getActiveClass('starred')}`} to={siteRoot + 'starred/'} title={gettext('Favorites')} onClick={(e) => this.tabItemClick(e, 'starred')}>
|
||||
<span className="sf2-icon-star" aria-hidden="true"></span>
|
||||
|
@ -29,7 +29,6 @@ const propTypes = {
|
||||
onFreezedItem: PropTypes.func.isRequired,
|
||||
onUnfreezedItem: PropTypes.func.isRequired,
|
||||
onItemUnshare: PropTypes.func.isRequired,
|
||||
onItemDetails: PropTypes.func,
|
||||
onItemRename: PropTypes.func,
|
||||
onItemDelete: PropTypes.func,
|
||||
onMonitorRepo: PropTypes.func
|
||||
@ -165,9 +164,6 @@ class SharedRepoListItem extends React.Component {
|
||||
case 'Folder Permission':
|
||||
this.onItemFolderPermissionToggle();
|
||||
break;
|
||||
case 'Details':
|
||||
this.onItemDetails();
|
||||
break;
|
||||
case 'Share':
|
||||
this.onItemShare();
|
||||
break;
|
||||
@ -251,10 +247,6 @@ class SharedRepoListItem extends React.Component {
|
||||
this.setState({isHistorySettingDialogShow: !this.state.isHistorySettingDialogShow});
|
||||
};
|
||||
|
||||
onItemDetails = () => {
|
||||
this.props.onItemDetails(this.props.repo);
|
||||
};
|
||||
|
||||
onItemShare = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({isShowSharedDialog: true});
|
||||
@ -338,9 +330,6 @@ class SharedRepoListItem extends React.Component {
|
||||
case 'Folder Permission':
|
||||
translateResult = gettext('Folder Permission');
|
||||
break;
|
||||
case 'Details':
|
||||
translateResult = gettext('Details');
|
||||
break;
|
||||
case 'Unshare':
|
||||
translateResult = gettext('Unshare');
|
||||
break;
|
||||
@ -423,7 +412,7 @@ class SharedRepoListItem extends React.Component {
|
||||
const monitorOp = repo.monitored ? 'Unwatch File Changes' : 'Watch File Changes';
|
||||
operations.push(monitorOp);
|
||||
}
|
||||
operations.push('Divider', 'History Setting', 'Details');
|
||||
operations.push('Divider', 'History Setting');
|
||||
if (Utils.isDesktop()) {
|
||||
operations.push('Advanced');
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ const propTypes = {
|
||||
repoList: PropTypes.array.isRequired,
|
||||
onItemUnshare: PropTypes.func.isRequired,
|
||||
onItemDelete: PropTypes.func,
|
||||
onItemDetails: PropTypes.func,
|
||||
onItemRename: PropTypes.func,
|
||||
hasNextPage: PropTypes.bool,
|
||||
onMonitorRepo: PropTypes.func,
|
||||
@ -98,7 +97,6 @@ class SharedRepoListView extends React.Component {
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
onItemUnshare={this.props.onItemUnshare}
|
||||
onItemDelete={this.props.onItemDelete}
|
||||
onItemDetails={this.props.onItemDetails}
|
||||
onItemRename={this.props.onItemRename}
|
||||
onMonitorRepo={this.props.onMonitorRepo}
|
||||
/>
|
||||
@ -109,12 +107,11 @@ class SharedRepoListView extends React.Component {
|
||||
};
|
||||
|
||||
renderPCUI = () => {
|
||||
let isShowTableThread = this.props.isShowTableThread !== undefined ? this.props.isShowTableThread : true;
|
||||
|
||||
const { theadHidden = false } = this.props;
|
||||
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
|
||||
|
||||
return (
|
||||
<table className={isShowTableThread ? '' : 'table-thead-hidden'}>
|
||||
<table className={theadHidden ? 'table-thead-hidden' : ''}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"></th>
|
||||
|
@ -1,44 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import CommonToolbar from './common-toolbar';
|
||||
import { Button } from 'reactstrap';
|
||||
import { gettext, canAddGroup } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import CreateGroupDialog from '../../components/dialog/create-group-dialog';
|
||||
|
||||
const propTypes = {
|
||||
searchPlaceholder: PropTypes.string,
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
toggleAddGroupModal: PropTypes.func.isRequired,
|
||||
onCreateGroup: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class GroupsToolbar extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleDialog = () => {
|
||||
this.setState({
|
||||
isDialogOpen: !this.state.isDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let { onShowSidePanel, onSearchedClick } = this.props;
|
||||
return (
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span title="Side Nav Menu" onClick={onShowSidePanel} className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none"></span>
|
||||
{canAddGroup && (
|
||||
<div className="operation">
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<Button color="btn btn-secondary operation-item" onClick={this.props.toggleAddGroupModal}>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
|
||||
</Button>
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.props.toggleAddGroupModal}></span>
|
||||
</MediaQuery>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick}/>
|
||||
</div>
|
||||
<>
|
||||
{Utils.isDesktop() ? (
|
||||
<div className="operation">
|
||||
<button className="btn btn-secondary operation-item" onClick={this.toggleDialog}>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.toggleDialog}></span>
|
||||
)}
|
||||
{this.state.isDialogOpen &&
|
||||
<CreateGroupDialog
|
||||
toggleDialog={this.toggleDialog}
|
||||
onCreateGroup={this.props.onCreateGroup}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,23 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { DropdownToggle, Dropdown, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import { Link, navigate } from '@gatsbyjs/reach-router';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import ModalPortal from '../modal-portal';
|
||||
import CreateRepoDialog from '../dialog/create-repo-dialog';
|
||||
import { DropdownToggle, Dropdown, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
|
||||
const propTypes = {
|
||||
libraryType: PropTypes.string.isRequired,
|
||||
onCreateRepo: PropTypes.func.isRequired,
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
moreShown: PropTypes.bool
|
||||
};
|
||||
|
||||
class RepoViewToolbar extends React.Component {
|
||||
class MyLibsToolbar extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isCreateRepoDialogShow: false,
|
||||
isCreateRepoDialogOpen: false,
|
||||
isOpen: false,
|
||||
};
|
||||
}
|
||||
@ -29,7 +28,7 @@ class RepoViewToolbar extends React.Component {
|
||||
};
|
||||
|
||||
onCreateToggle = () => {
|
||||
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
|
||||
this.setState({isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen});
|
||||
};
|
||||
|
||||
toggleMore = () => {
|
||||
@ -49,15 +48,15 @@ class RepoViewToolbar extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { moreShown = false } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
{Utils.isDesktop() ? (
|
||||
<div className="operation">
|
||||
<button className="btn btn-secondary operation-item" title={gettext('New Library')} onClick={this.onCreateToggle}>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Library')}
|
||||
</button>
|
||||
{Utils.isDesktop() ? (
|
||||
<div className="operation">
|
||||
<button className="btn btn-secondary operation-item" title={gettext('New Library')} onClick={this.onCreateToggle}>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Library')}
|
||||
</button>
|
||||
{moreShown &&
|
||||
<Dropdown isOpen={this.state.isOpen} toggle={this.toggleMore}>
|
||||
<DropdownToggle className='btn btn-secondary operation-item' onKeyDown={this.onDropdownToggleKeyDown}>
|
||||
{gettext('More')}
|
||||
@ -68,15 +67,15 @@ class RepoViewToolbar extends React.Component {
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
) : (
|
||||
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Library')} onClick={this.onCreateToggle}></span>
|
||||
)}
|
||||
</div>
|
||||
{this.state.isCreateRepoDialogShow && (
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Library')} onClick={this.onCreateToggle}></span>
|
||||
)}
|
||||
{this.state.isCreateRepoDialogOpen && (
|
||||
<ModalPortal>
|
||||
<CreateRepoDialog
|
||||
libraryType={this.props.libraryType}
|
||||
libraryType='mine'
|
||||
onCreateRepo={this.onCreateRepo}
|
||||
onCreateToggle={this.onCreateToggle}
|
||||
/>
|
||||
@ -87,6 +86,6 @@ class RepoViewToolbar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
RepoViewToolbar.propTypes = propTypes;
|
||||
MyLibsToolbar.propTypes = propTypes;
|
||||
|
||||
export default RepoViewToolbar;
|
||||
export default MyLibsToolbar;
|
100
frontend/src/pages/groups/group-item.js
Normal file
100
frontend/src/pages/groups/group-item.js
Normal file
@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||
|
||||
import '../../css/groups.css';
|
||||
|
||||
const propTypes = {
|
||||
group: PropTypes.object.isRequired,
|
||||
updateGroup: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
class GroupItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onItemUnshare = (repo) => {
|
||||
const { group } = this.props;
|
||||
seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => {
|
||||
group.repos = group.repos.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.props.updateGroup(group);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onItemDelete = (repo) => {
|
||||
const { group } = this.props;
|
||||
group.repos = group.repos.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.props.updateGroup(group);
|
||||
};
|
||||
|
||||
onItemRename = (repo, newName) => {
|
||||
let group = this.props.group;
|
||||
seafileAPI.renameGroupOwnedLibrary(group.id, repo.repo_id, newName).then(res => {
|
||||
const { group } = this.props;
|
||||
group.repos = group.repos.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.repo_name = newName;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.props.updateGroup(group);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onMonitorRepo = (repo, monitored) => {
|
||||
const { group } = this.props;
|
||||
group.repos = group.repos.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.monitored = monitored;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.props.updateGroup(group);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
|
||||
return (
|
||||
<div className="group-list-panel">
|
||||
<h4 className="group-item-heading ellipsis">
|
||||
<a href={`${siteRoot}group/${group.id}/`} title={group.name}>{group.name}</a>
|
||||
</h4>
|
||||
{group.repos.length === 0 ?
|
||||
emptyTip :
|
||||
<SharedRepoListView
|
||||
theadHidden={true}
|
||||
isShowRepoOwner={false}
|
||||
currentGroup={this.props.group}
|
||||
repoList={group.repos}
|
||||
onItemUnshare={this.onItemUnshare}
|
||||
onItemDelete={this.onItemDelete}
|
||||
onItemRename={this.onItemRename}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GroupItem.propTypes = propTypes;
|
||||
|
||||
export default GroupItem;
|
@ -21,7 +21,6 @@ import ImportMembersDialog from '../../components/dialog/import-members-dialog';
|
||||
import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
|
||||
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||
|
||||
import '../../css/group-view.css';
|
||||
@ -65,7 +64,6 @@ class GroupView extends React.Component {
|
||||
showImportMembersDialog: false,
|
||||
showManageMembersDialog: false,
|
||||
groupMembers: [],
|
||||
isShowDetails: false,
|
||||
isLeaveGroupDialogOpen: false,
|
||||
};
|
||||
}
|
||||
@ -349,17 +347,6 @@ class GroupView extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
onItemDetails = (repo) => {
|
||||
this.setState({
|
||||
isShowDetails: true,
|
||||
currentRepo: repo,
|
||||
});
|
||||
};
|
||||
|
||||
closeDetails = () => {
|
||||
this.setState({isShowDetails: false});
|
||||
};
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||
@ -568,18 +555,12 @@ class GroupView extends React.Component {
|
||||
sortItems={this.sortItems}
|
||||
onItemUnshare={this.onItemUnshare}
|
||||
onItemDelete={this.onItemDelete}
|
||||
onItemDetails={this.onItemDetails}
|
||||
onItemRename={this.onItemRename}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isShowDetails && (
|
||||
<div className="cur-view-detail">
|
||||
<LibDetail currentRepo={this.state.currentRepo} closeDetails={this.closeDetails}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && (
|
||||
<ModalPortal>
|
||||
|
@ -1,119 +1,18 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, siteRoot, canAddGroup } from '../../utils/constants';
|
||||
import { gettext, canAddGroup } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Loading from '../../components/loading';
|
||||
import Group from '../../models/group';
|
||||
import Repo from '../../models/repo';
|
||||
import toaster from '../../components/toast';
|
||||
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||
import GroupsToolbar from '../../components/toolbar/groups-toolbar';
|
||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||
import CreateGroupDialog from '../../components/dialog/create-group-dialog';
|
||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
import GroupItem from './group-item';
|
||||
|
||||
import '../../css/groups.css';
|
||||
|
||||
const propTypes = {
|
||||
group: PropTypes.object.isRequired,
|
||||
onItemDetails: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
class RepoListViewPanel extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
repoList: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let group = this.props.group;
|
||||
let repoList = group.repos.map(item => {
|
||||
let repo = new Repo(item);
|
||||
return repo;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
}
|
||||
|
||||
onItemUnshare = (repo) => {
|
||||
let group = this.props.group;
|
||||
seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onItemDelete = (repo) => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
onItemRename = (repo, newName) => {
|
||||
let group = this.props.group;
|
||||
seafileAPI.renameGroupOwnedLibrary(group.id, repo.repo_id, newName).then(res => {
|
||||
let repoList = this.state.repoList.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.repo_name = newName;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onMonitorRepo = (repo, monitored) => {
|
||||
let repoList = this.state.repoList.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.monitored = monitored;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
render() {
|
||||
let group = this.props.group;
|
||||
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
|
||||
return (
|
||||
<div className="group-list-panel">
|
||||
<h4 className="group-item-heading ellipsis">
|
||||
<a href={`${siteRoot}group/${group.id}/`} title={group.name}>{group.name}</a>
|
||||
</h4>
|
||||
{this.state.repoList.length === 0 ?
|
||||
emptyTip :
|
||||
<SharedRepoListView
|
||||
isShowTableThread={false}
|
||||
isShowRepoOwner={false}
|
||||
currentGroup={this.props.group}
|
||||
repoList={this.state.repoList}
|
||||
onItemUnshare={this.onItemUnshare}
|
||||
onItemDelete={this.onItemDelete}
|
||||
onItemDetails={this.props.onItemDetails}
|
||||
onItemRename={this.onItemRename}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RepoListViewPanel.propTypes = propTypes;
|
||||
|
||||
class GroupsView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
@ -121,10 +20,7 @@ class GroupsView extends React.Component {
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
errorMsg: '',
|
||||
groupList: [],
|
||||
showAddGroupModal: false,
|
||||
isShowDetails: false,
|
||||
currentRepo: null,
|
||||
groupList: []
|
||||
};
|
||||
}
|
||||
|
||||
@ -133,6 +29,9 @@ class GroupsView extends React.Component {
|
||||
// `{'with_repos': 1}`: list repos of every group
|
||||
let groupList = res.data.map(item => {
|
||||
let group = new Group(item);
|
||||
group.repos = item.repos.map(item => {
|
||||
return new Repo(item);
|
||||
});
|
||||
return group;
|
||||
});
|
||||
this.setState({
|
||||
@ -149,36 +48,31 @@ class GroupsView extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
toggleAddGroupModal = () => {
|
||||
onCreateGroup = (groupData) => {
|
||||
const newGroup = new Group(groupData);
|
||||
const { groupList: newList } = this.state;
|
||||
newList.unshift(newGroup);
|
||||
this.setState({
|
||||
showAddGroupModal: !this.state.showAddGroupModal
|
||||
groupList: newList
|
||||
});
|
||||
};
|
||||
|
||||
onCreateGroup = () => {
|
||||
this.setState({
|
||||
showAddGroupModal: false,
|
||||
isLoading: true,
|
||||
groupList: [],
|
||||
});
|
||||
this.listGroups();
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.listGroups();
|
||||
}
|
||||
|
||||
onItemDetails = (repo) => {
|
||||
updateGroup = (group) => {
|
||||
const { groupList } = this.state;
|
||||
this.setState({
|
||||
isShowDetails: true,
|
||||
currentRepo: repo,
|
||||
groupList: groupList.map((item) => {
|
||||
if (item.id == group.id) {
|
||||
item = group;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
closeDetails = () => {
|
||||
this.setState({isShowDetails: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
@ -192,11 +86,12 @@ class GroupsView extends React.Component {
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<GroupsToolbar
|
||||
<TopToolbar
|
||||
onShowSidePanel={this.props.onShowSidePanel}
|
||||
onSearchedClick={this.props.onSearchedClick}
|
||||
toggleAddGroupModal={this.toggleAddGroupModal}
|
||||
/>
|
||||
>
|
||||
{canAddGroup && <GroupsToolbar onCreateGroup={this.onCreateGroup} />}
|
||||
</TopToolbar>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
@ -208,28 +103,16 @@ class GroupsView extends React.Component {
|
||||
{(!this.state.isLoading && !this.state.errorMsg && this.state.groupList.length == 0) && emptyTip}
|
||||
{!this.state.isLoading && this.state.groupList.map((group, index) => {
|
||||
return (
|
||||
<RepoListViewPanel
|
||||
<GroupItem
|
||||
key={index}
|
||||
group={group}
|
||||
onItemDetails={this.onItemDetails}
|
||||
updateGroup={this.updateGroup}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isShowDetails && (
|
||||
<div className="cur-view-detail">
|
||||
<LibDetail currentRepo={this.state.currentRepo} closeDetails={this.closeDetails}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{ this.state.showAddGroupModal &&
|
||||
<CreateGroupDialog
|
||||
toggleAddGroupModal={this.toggleAddGroupModal}
|
||||
onCreateGroup={this.onCreateGroup}
|
||||
showAddGroupModal={this.state.showAddGroupModal}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
305
frontend/src/pages/libraries/index.js
Normal file
305
frontend/src/pages/libraries/index.js
Normal file
@ -0,0 +1,305 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cookie from 'react-cookies';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, canAddRepo, canViewOrg, canAddGroup } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import Repo from '../../models/repo';
|
||||
import Group from '../../models/group';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||
import MyLibsToolbar from '../../components/toolbar/my-libs-toolbar';
|
||||
import GroupsToolbar from '../../components/toolbar/groups-toolbar';
|
||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
|
||||
import MylibRepoListView from '../../pages/my-libs/mylib-repo-list-view';
|
||||
import SharedLibs from '../../pages/shared-libs/shared-libs.js';
|
||||
import SharedWithAll from '../../pages/shared-with-all';
|
||||
import GroupItem from '../../pages/groups/group-item';
|
||||
|
||||
const propTypes = {
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class Libraries extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
// for 'my libs'
|
||||
errorMsg: '',
|
||||
isLoading: true,
|
||||
repoList: [],
|
||||
isSortOptionsDialogOpen: false,
|
||||
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
|
||||
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
||||
|
||||
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
|
||||
|
||||
// for 'groups'
|
||||
isGroupsLoading: true,
|
||||
groupsErrorMsg: '',
|
||||
groupList: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listMyLibs();
|
||||
this.listGroups();
|
||||
}
|
||||
|
||||
listMyLibs = () => {
|
||||
seafileAPI.listRepos({type: 'mine'}).then((res) => {
|
||||
let repoList = res.data.repos.map((item) => {
|
||||
return new Repo(item);
|
||||
});
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
toggleSortOptionsDialog = () => {
|
||||
this.setState({
|
||||
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
onCreateRepo = (repo) => {
|
||||
seafileAPI.createMineRepo(repo).then((res) => {
|
||||
const newRepo = new Repo({
|
||||
repo_id: res.data.repo_id,
|
||||
repo_name: res.data.repo_name,
|
||||
size: res.data.repo_size,
|
||||
mtime: res.data.mtime,
|
||||
owner_email: res.data.email,
|
||||
encrypted: res.data.encrypted,
|
||||
permission: res.data.permission,
|
||||
storage_name: res.data.storage_name
|
||||
});
|
||||
this.state.repoList.unshift(newRepo);
|
||||
this.setState({repoList: this.state.repoList});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
sortRepoList = (sortBy, sortOrder) => {
|
||||
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
|
||||
});
|
||||
};
|
||||
|
||||
onTransferRepo = (repoID) => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repoID;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
onRenameRepo = (repo, newName) => {
|
||||
let repoList = this.state.repoList.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.repo_name = newName;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
onMonitorRepo = (repo, monitored) => {
|
||||
let repoList = this.state.repoList.map(item => {
|
||||
if (item.repo_id === repo.repo_id) {
|
||||
item.monitored = monitored;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
onDeleteRepo = (repo) => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
toggleGuideForNewDialog = () => {
|
||||
window.app.pageOptions.guideEnabled = false;
|
||||
this.setState({
|
||||
isGuideForNewDialogOpen: false
|
||||
});
|
||||
};
|
||||
|
||||
// the following are for 'groups'
|
||||
listGroups = () => {
|
||||
seafileAPI.listGroups(true).then((res) => {
|
||||
// `{'with_repos': 1}`: list repos of every group
|
||||
let groupList = res.data.map(item => {
|
||||
let group = new Group(item);
|
||||
group.repos = item.repos.map(item => {
|
||||
return new Repo(item);
|
||||
});
|
||||
return group;
|
||||
});
|
||||
this.setState({
|
||||
isGroupsLoading: false,
|
||||
groupList: groupList.sort((a, b) => {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
})
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
isGroupsLoading: false,
|
||||
groupsErrorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onCreateGroup = (groupData) => {
|
||||
const newGroup = new Group(groupData);
|
||||
const { groupList: newList } = this.state;
|
||||
newList.unshift(newGroup);
|
||||
this.setState({
|
||||
groupList: newList
|
||||
});
|
||||
};
|
||||
|
||||
updateGroup = (group) => {
|
||||
const { groupList } = this.state;
|
||||
this.setState({
|
||||
groupList: groupList.map((item) => {
|
||||
if (item.id == group.id) {
|
||||
item = group;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No libraries')}</h2>
|
||||
<p>{gettext('You have not created any libraries yet. A library is a container to organize your files and folders. A library can also be shared with others and synced to your connected devices. You can create a library by clicking the "New Library" button in the menu bar.')}</p>
|
||||
</EmptyTip>
|
||||
);
|
||||
|
||||
const groupsEmptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No groups')}</h2>
|
||||
{canAddGroup ?
|
||||
<p>{gettext('You are not in any groups. Groups allow multiple people to collaborate on libraries. You can create a group by clicking the "New Group" button in the menu bar.')}</p> :
|
||||
<p>{gettext('You are not in any groups. Groups allow multiple people to collaborate on libraries. Groups you join will be listed here.')}</p>
|
||||
}
|
||||
</EmptyTip>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TopToolbar
|
||||
onShowSidePanel={this.props.onShowSidePanel}
|
||||
onSearchedClick={this.props.onSearchedClick}
|
||||
>
|
||||
<>
|
||||
{canAddRepo && <MyLibsToolbar onCreateRepo={this.onCreateRepo} />}
|
||||
{canAddGroup && <GroupsToolbar onCreateGroup={this.onCreateGroup} />}
|
||||
</>
|
||||
</TopToolbar>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Files')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{canAddRepo && (
|
||||
<div className="pb-4">
|
||||
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
|
||||
<h4 className="sf-heading m-0">{gettext('My Libraries')}</h4>
|
||||
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||
</div>
|
||||
{this.state.isLoading ? <Loading /> : (
|
||||
this.state.errorMsg ? <p className="error text-center mt-8">{this.state.errorMsg}</p> : (
|
||||
this.state.repoList.length === 0 ? emptyTip : (
|
||||
<MylibRepoListView
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
repoList={this.state.repoList}
|
||||
onRenameRepo={this.onRenameRepo}
|
||||
onDeleteRepo={this.onDeleteRepo}
|
||||
onTransferRepo={this.onTransferRepo}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
onRepoClick={this.onRepoClick}
|
||||
sortRepoList={this.sortRepoList}
|
||||
theadHidden={true}
|
||||
/>
|
||||
)))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pb-4">
|
||||
<SharedLibs inAllLibs={true} />
|
||||
</div>
|
||||
|
||||
{canViewOrg && (
|
||||
<div className="pb-4">
|
||||
<SharedWithAll inAllLibs={true} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pb-4">
|
||||
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
|
||||
<h4 className="sf-heading m-0">{gettext('Shared with groups')}</h4>
|
||||
</div>
|
||||
{this.state.isGroupsLoading? <Loading /> : (
|
||||
this.state.groupsErrorMsg ? <p className="error text-center mt-8">{this.state.groupsErrorMsg}</p> : (
|
||||
this.state.groupList.length === 0 ? groupsEmptyTip : (
|
||||
this.state.groupList.map((group, index) => {
|
||||
return (
|
||||
<GroupItem
|
||||
key={index}
|
||||
group={group}
|
||||
updateGroup={this.updateGroup}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen &&
|
||||
<GuideForNewDialog
|
||||
toggleDialog={this.toggleGuideForNewDialog}
|
||||
/>
|
||||
}
|
||||
{this.state.isSortOptionsDialogOpen &&
|
||||
<SortOptionsDialog
|
||||
toggleDialog={this.toggleSortOptionsDialog}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortRepoList}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Libraries.propTypes = propTypes;
|
||||
|
||||
export default Libraries;
|
@ -8,12 +8,10 @@ import toaster from '../../components/toast';
|
||||
import Repo from '../../models/repo';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
|
||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
||||
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||
import MyLibsToolbar from '../../components/toolbar/my-libs-toolbar';
|
||||
import MylibRepoListView from './mylib-repo-list-view';
|
||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
|
||||
|
||||
const propTypes = {
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
@ -27,9 +25,7 @@ class MyLibraries extends Component {
|
||||
errorMsg: '',
|
||||
isLoading: true,
|
||||
repoList: [],
|
||||
isShowDetails: false,
|
||||
isSortOptionsDialogOpen: false,
|
||||
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
|
||||
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
|
||||
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
||||
};
|
||||
@ -129,37 +125,15 @@ class MyLibraries extends Component {
|
||||
this.setState({repoList: repoList});
|
||||
};
|
||||
|
||||
onRepoClick = (repo) => {
|
||||
if (this.state.isShowDetails) {
|
||||
this.onRepoDetails(repo);
|
||||
}
|
||||
};
|
||||
|
||||
onRepoDetails = (repo) => {
|
||||
this.setState({
|
||||
currentRepo: repo,
|
||||
isShowDetails: true,
|
||||
});
|
||||
};
|
||||
|
||||
closeDetails = () => {
|
||||
this.setState({isShowDetails: !this.state.isShowDetails});
|
||||
};
|
||||
|
||||
toggleGuideForNewDialog = () => {
|
||||
window.app.pageOptions.guideEnabled = false;
|
||||
this.setState({
|
||||
isGuideForNewDialogOpen: false
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north border-left-show">
|
||||
<RepoViewToolbar onShowSidePanel={this.props.onShowSidePanel} onCreateRepo={this.onCreateRepo} libraryType={'mine'}/>
|
||||
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
|
||||
</div>
|
||||
<TopToolbar
|
||||
onShowSidePanel={this.props.onShowSidePanel}
|
||||
onSearchedClick={this.props.onSearchedClick}
|
||||
>
|
||||
<MyLibsToolbar onCreateRepo={this.onCreateRepo} moreShown={true} />
|
||||
</TopToolbar>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
@ -179,17 +153,11 @@ class MyLibraries extends Component {
|
||||
onDeleteRepo={this.onDeleteRepo}
|
||||
onTransferRepo={this.onTransferRepo}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
onRepoClick={this.onRepoClick}
|
||||
sortRepoList={this.sortRepoList}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen &&
|
||||
<GuideForNewDialog
|
||||
toggleDialog={this.toggleGuideForNewDialog}
|
||||
/>
|
||||
}
|
||||
{this.state.isSortOptionsDialogOpen &&
|
||||
<SortOptionsDialog
|
||||
toggleDialog={this.toggleSortOptionsDialog}
|
||||
@ -198,14 +166,6 @@ class MyLibraries extends Component {
|
||||
sortItems={this.sortRepoList}
|
||||
/>
|
||||
}
|
||||
{this.state.isShowDetails && (
|
||||
<div className="cur-view-detail">
|
||||
<LibDetail
|
||||
currentRepo={this.state.currentRepo}
|
||||
closeDetails={this.closeDetails}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -32,7 +32,6 @@ const propTypes = {
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
onTransferRepo: PropTypes.func.isRequired,
|
||||
onRepoClick: PropTypes.func.isRequired,
|
||||
onMonitorRepo: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -148,10 +147,6 @@ class MylibRepoListItem extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
onRepoClick = () => {
|
||||
this.props.onRepoClick(this.props.repo);
|
||||
};
|
||||
|
||||
onToggleStarRepo = (e) => {
|
||||
e.preventDefault();
|
||||
const repoName = this.props.repo.repo_name;
|
||||
@ -328,7 +323,7 @@ class MylibRepoListItem extends React.Component {
|
||||
let iconTitle = Utils.getLibIconTitle(repo);
|
||||
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||
return (
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick} onFocus={this.onFocus}>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onFocus}>
|
||||
<td className="text-center">
|
||||
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
||||
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
||||
@ -382,7 +377,7 @@ class MylibRepoListItem extends React.Component {
|
||||
let repoURL = this.repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||
|
||||
return (
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick}>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td onClick={this.visitRepo}>
|
||||
{this.state.isRenaming && (
|
||||
|
@ -13,8 +13,8 @@ const propTypes = {
|
||||
onRenameRepo: PropTypes.func.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
onTransferRepo: PropTypes.func.isRequired,
|
||||
onRepoClick: PropTypes.func.isRequired,
|
||||
onMonitorRepo: PropTypes.func.isRequired,
|
||||
theadHidden : PropTypes.bool, // for 'My Libraries' in 'Files' page
|
||||
};
|
||||
|
||||
class MylibRepoListView extends React.Component {
|
||||
@ -70,7 +70,6 @@ class MylibRepoListView extends React.Component {
|
||||
onDeleteRepo={this.props.onDeleteRepo}
|
||||
onTransferRepo={this.props.onTransferRepo}
|
||||
onMonitorRepo={this.props.onMonitorRepo}
|
||||
onRepoClick={this.props.onRepoClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -79,10 +78,11 @@ class MylibRepoListView extends React.Component {
|
||||
};
|
||||
|
||||
renderPCUI = () => {
|
||||
const { theadHidden } = this.props;
|
||||
const showStorageBackend = storages.length > 0;
|
||||
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
||||
return (
|
||||
<table>
|
||||
<table className={theadHidden ? 'table-thead-hidden' : ''}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"></th>
|
||||
|
@ -54,7 +54,7 @@ class Content extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items, sortBy, sortOrder } = this.props;
|
||||
const { loading, errorMsg, items, sortBy, sortOrder, theadHidden } = this.props;
|
||||
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
@ -90,7 +90,7 @@ class Content extends Component {
|
||||
|
||||
const isDesktop = Utils.isDesktop();
|
||||
const table = (
|
||||
<table className={isDesktop ? '' : 'table-thead-hidden'}>
|
||||
<table className={(isDesktop && !theadHidden)? '' : 'table-thead-hidden'}>
|
||||
{isDesktop ? desktopThead : <LibsMobileThead />}
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
@ -113,6 +113,7 @@ class Content extends Component {
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
theadHidden: PropTypes.bool.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
@ -447,28 +448,55 @@ class SharedLibraries extends Component {
|
||||
this.setState({items: items});
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||
return (
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
theadHidden={inAllLibs}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderSortIconInMobile = () => {
|
||||
return (
|
||||
<>
|
||||
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared with me')}</h3>
|
||||
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||
{inAllLibs ? (
|
||||
<>
|
||||
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
|
||||
<h4 className="sf-heading m-0">{gettext('Shared with me')}</h4>
|
||||
{this.renderSortIconInMobile()}
|
||||
</div>
|
||||
<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}
|
||||
onMonitorRepo={this.onMonitorRepo}
|
||||
/>
|
||||
{this.renderContent()}
|
||||
</>
|
||||
) : (
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared with me')}</h3>
|
||||
{this.renderSortIconInMobile()}
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.state.isSortOptionsDialogOpen &&
|
||||
<SortOptionsDialog
|
||||
toggleDialog={this.toggleSortOptionsDialog}
|
||||
@ -482,4 +510,8 @@ class SharedLibraries extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
SharedLibraries.propTypes = {
|
||||
inAllLibs: PropTypes.bool
|
||||
};
|
||||
|
||||
export default SharedLibraries;
|
||||
|
198
frontend/src/pages/shared-with-all/index.js
Normal file
198
frontend/src/pages/shared-with-all/index.js
Normal file
@ -0,0 +1,198 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cookie from 'react-cookies';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, canAddPublicRepo } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Repo from '../../models/repo';
|
||||
import toaster from '../../components/toast';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||
import TopToolbar from './top-toolbar';
|
||||
|
||||
const propTypes = {
|
||||
onShowSidePanel: PropTypes.func,
|
||||
onSearchedClick: PropTypes.func,
|
||||
inAllLibs: PropTypes.bool
|
||||
};
|
||||
|
||||
class PublicSharedView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
errMessage: '',
|
||||
repoList: [],
|
||||
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
|
||||
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
||||
isSortOptionsDialogOpen: false,
|
||||
libraryType: 'public',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listRepos({type: 'public'}).then((res) => {
|
||||
let repoList = res.data.repos.map(item => {
|
||||
let repo = new Repo(item);
|
||||
return repo;
|
||||
});
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onItemUnshare = (repo) => {
|
||||
seafileAPI.unshareRepo(repo.repo_id, {share_type: 'public'}).then(() => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
let message = gettext('Successfully unshared {name}').replace('{name}', repo.repo_name);
|
||||
toaster.success(message);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
if (errMessage === gettext('Error')) {
|
||||
errMessage = gettext('Failed to unshare {name}').replace('{name}', repo.repo_name);
|
||||
}
|
||||
toaster(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onItemDelete = () => {
|
||||
// todo need to optimized
|
||||
};
|
||||
|
||||
addRepoItem = (repo) => {
|
||||
let isExist = false;
|
||||
let repoIndex = 0;
|
||||
let repoList = this.state.repoList;
|
||||
for (let i = 0; i < repoList.length; i ++) {
|
||||
if (repo.repo_id === repoList[i].repo_id) {
|
||||
isExist = true;
|
||||
repoIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isExist) {
|
||||
this.state.repoList.splice(repoIndex, 1);
|
||||
}
|
||||
|
||||
let newRepoList = this.state.repoList.map(item => {return item;});
|
||||
newRepoList.unshift(repo);
|
||||
this.setState({repoList: newRepoList});
|
||||
};
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
|
||||
});
|
||||
};
|
||||
|
||||
toggleSortOptionsDialog = () => {
|
||||
this.setState({
|
||||
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
const { errMessage } = this.state;
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No public libraries')}</h2>
|
||||
<p>{gettext('No public libraries have been created yet. A public library is accessible by all users. You can create a public library by clicking the "Add Library" button in the menu bar.')}</p>
|
||||
</EmptyTip>
|
||||
);
|
||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||
return (
|
||||
<>
|
||||
{this.state.isLoading && <Loading />}
|
||||
{(!this.state.isLoading && errMessage) && errMessage}
|
||||
{(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip}
|
||||
{(!this.state.isLoading && this.state.repoList.length > 0) &&
|
||||
<SharedRepoListView
|
||||
libraryType={this.state.libraryType}
|
||||
repoList={this.state.repoList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onItemUnshare={this.onItemUnshare}
|
||||
onItemDelete={this.onItemDelete}
|
||||
theadHidden={inAllLibs}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderSortIconInMobile = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||
|
||||
if (inAllLibs) {
|
||||
return (
|
||||
<>
|
||||
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
|
||||
<h4 className="sf-heading m-0">{gettext('Shared with all')}</h4>
|
||||
{this.renderSortIconInMobile()}
|
||||
</div>
|
||||
{this.renderContent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north border-left-show">
|
||||
{canAddPublicRepo && <TopToolbar onShowSidePanel={this.props.onShowSidePanel} addRepoItem={this.addRepoItem} />}
|
||||
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
|
||||
</div>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared with all')}</h3>
|
||||
{this.renderSortIconInMobile()}
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isSortOptionsDialogOpen &&
|
||||
<SortOptionsDialog
|
||||
toggleDialog={this.toggleSortOptionsDialog}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PublicSharedView.propTypes = propTypes;
|
||||
|
||||
export default PublicSharedView;
|
@ -1,259 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cookie from 'react-cookies';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, canAddPublicRepo } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Repo from '../../models/repo';
|
||||
import toaster from '../../components/toast';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
||||
import ShareRepoDialog from '../../components/dialog/share-repo-dialog';
|
||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||
|
||||
const propTypes = {
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class PublicSharedView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
errMessage: '',
|
||||
emptyTip: '',
|
||||
repoList: [],
|
||||
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
|
||||
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
||||
isSortOptionsDialogOpen: false,
|
||||
libraryType: 'public',
|
||||
isCreateMenuShow: false,
|
||||
isCreateRepoDialogShow: false,
|
||||
isSelectRepoDialpgShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listRepos({type: 'public'}).then((res) => {
|
||||
let repoList = res.data.repos.map(item => {
|
||||
let repo = new Repo(item);
|
||||
return repo;
|
||||
});
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onCreateRepo = (repo) => {
|
||||
seafileAPI.createPublicRepo(repo).then(res => {
|
||||
let object = { // need modify api return value
|
||||
repo_id: res.data.id,
|
||||
repo_name: res.data.name,
|
||||
permission: res.data.permission,
|
||||
size: res.data.size,
|
||||
owner_name: res.data.owner_name,
|
||||
owner_email: res.data.owner,
|
||||
mtime: res.data.mtime,
|
||||
encrypted: res.data.encrypted,
|
||||
};
|
||||
let repo = new Repo(object);
|
||||
let repoList = this.addRepoItem(repo);
|
||||
this.setState({repoList: repoList});
|
||||
this.onCreateRepoToggle();
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onRepoSelectedHandler = (selectedRepoList) => {
|
||||
selectedRepoList.forEach(repo => {
|
||||
seafileAPI.selectOwnedRepoToPublic(repo.repo_id, {share_type: 'public', permission: repo.sharePermission}).then(() => {
|
||||
let repoList = this.addRepoItem(repo);
|
||||
this.setState({repoList: repoList});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onItemUnshare = (repo) => {
|
||||
seafileAPI.unshareRepo(repo.repo_id, {share_type: 'public'}).then(() => {
|
||||
let repoList = this.state.repoList.filter(item => {
|
||||
return item.repo_id !== repo.repo_id;
|
||||
});
|
||||
this.setState({repoList: repoList});
|
||||
let message = gettext('Successfully unshared {name}').replace('{name}', repo.repo_name);
|
||||
toaster.success(message);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
if (errMessage === gettext('Error')) {
|
||||
errMessage = gettext('Failed to unshare {name}').replace('{name}', repo.repo_name);
|
||||
}
|
||||
toaster(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onItemDelete = () => {
|
||||
// todo need to optimized
|
||||
};
|
||||
|
||||
addRepoItem = (repo) => {
|
||||
let isExist = false;
|
||||
let repoIndex = 0;
|
||||
let repoList = this.state.repoList;
|
||||
for (let i = 0; i < repoList.length; i ++) {
|
||||
if (repo.repo_id === repoList[i].repo_id) {
|
||||
isExist = true;
|
||||
repoIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isExist) {
|
||||
this.state.repoList.splice(repoIndex, 1);
|
||||
}
|
||||
|
||||
let newRepoList = this.state.repoList.map(item => {return item;});
|
||||
newRepoList.unshift(repo);
|
||||
return newRepoList;
|
||||
};
|
||||
|
||||
onAddRepoToggle = () => {
|
||||
this.setState({isCreateMenuShow: !this.state.isCreateMenuShow});
|
||||
};
|
||||
|
||||
onCreateRepoToggle = () => {
|
||||
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
|
||||
};
|
||||
|
||||
onSelectRepoToggle = () => {
|
||||
this.setState({isSelectRepoDialpgShow: !this.state.isSelectRepoDialpgShow});
|
||||
};
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
|
||||
});
|
||||
};
|
||||
|
||||
toggleSortOptionsDialog = () => {
|
||||
this.setState({
|
||||
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let errMessage = this.state.errMessage;
|
||||
let emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No public libraries')}</h2>
|
||||
<p>{gettext('No public libraries have been created yet. A public library is accessible by all users. You can create a public library by clicking the "Add Library" button in the menu bar.')}</p>
|
||||
</EmptyTip>
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-north border-left-show">
|
||||
{canAddPublicRepo &&
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
<div className="operation">
|
||||
<Dropdown isOpen={this.state.isCreateMenuShow} toggle={this.onAddRepoToggle}>
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<DropdownToggle className='btn btn-secondary operation-item'>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add Library')}
|
||||
</DropdownToggle>
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<DropdownToggle
|
||||
tag="span"
|
||||
className="sf2-icon-plus mobile-toolbar-icon"
|
||||
title={gettext('Add Library')}
|
||||
/>
|
||||
</MediaQuery>
|
||||
<DropdownMenu>
|
||||
<DropdownItem onClick={this.onSelectRepoToggle}>{gettext('Share existing libraries')}</DropdownItem>
|
||||
<DropdownItem onClick={this.onCreateRepoToggle}>{gettext('New Library')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
|
||||
</div>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared with all')}</h3>
|
||||
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.state.isLoading && <Loading />}
|
||||
{(!this.state.isLoading && errMessage) && errMessage}
|
||||
{(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip}
|
||||
{(!this.state.isLoading && this.state.repoList.length > 0) &&
|
||||
<SharedRepoListView
|
||||
libraryType={this.state.libraryType}
|
||||
repoList={this.state.repoList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onItemUnshare={this.onItemUnshare}
|
||||
onItemDelete={this.onItemDelete}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isSortOptionsDialogOpen &&
|
||||
<SortOptionsDialog
|
||||
toggleDialog={this.toggleSortOptionsDialog}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
/>
|
||||
}
|
||||
{this.state.isCreateRepoDialogShow && (
|
||||
<ModalPortal>
|
||||
<CreateRepoDialog
|
||||
libraryType={this.state.libraryType}
|
||||
onCreateToggle={this.onCreateRepoToggle}
|
||||
onCreateRepo={this.onCreateRepo}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isSelectRepoDialpgShow && (
|
||||
<ModalPortal>
|
||||
<ShareRepoDialog
|
||||
onRepoSelectedHandler={this.onRepoSelectedHandler}
|
||||
onShareRepoDialogClose={this.onSelectRepoToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PublicSharedView.propTypes = propTypes;
|
||||
|
||||
export default PublicSharedView;
|
125
frontend/src/pages/shared-with-all/top-toolbar.js
Normal file
125
frontend/src/pages/shared-with-all/top-toolbar.js
Normal file
@ -0,0 +1,125 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Repo from '../../models/repo';
|
||||
import toaster from '../../components/toast';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
||||
import ShareRepoDialog from '../../components/dialog/share-repo-dialog';
|
||||
|
||||
const propTypes = {
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
addRepoItem: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class TopToolbar extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
libraryType: 'public',
|
||||
isCreateMenuShow: false,
|
||||
isCreateRepoDialogShow: false,
|
||||
isSelectRepoDialpgShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
onCreateRepo = (repo) => {
|
||||
seafileAPI.createPublicRepo(repo).then(res => {
|
||||
let object = {
|
||||
repo_id: res.data.id,
|
||||
repo_name: res.data.name,
|
||||
permission: res.data.permission,
|
||||
size: res.data.size,
|
||||
owner_name: res.data.owner_name,
|
||||
owner_email: res.data.owner,
|
||||
mtime: res.data.mtime,
|
||||
encrypted: res.data.encrypted,
|
||||
};
|
||||
let repo = new Repo(object);
|
||||
this.props.addRepoItem(repo);
|
||||
this.onCreateRepoToggle();
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onRepoSelectedHandler = (selectedRepoList) => {
|
||||
selectedRepoList.forEach(repo => {
|
||||
seafileAPI.selectOwnedRepoToPublic(repo.repo_id, {share_type: 'public', permission: repo.sharePermission}).then(() => {
|
||||
this.props.addRepoItem(repo);
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onAddRepoToggle = () => {
|
||||
this.setState({isCreateMenuShow: !this.state.isCreateMenuShow});
|
||||
};
|
||||
|
||||
onCreateRepoToggle = () => {
|
||||
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
|
||||
};
|
||||
|
||||
onSelectRepoToggle = () => {
|
||||
this.setState({isSelectRepoDialpgShow: !this.state.isSelectRepoDialpgShow});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
|
||||
<div className="operation">
|
||||
<Dropdown isOpen={this.state.isCreateMenuShow} toggle={this.onAddRepoToggle}>
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<DropdownToggle className='btn btn-secondary operation-item'>
|
||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add Library')}
|
||||
</DropdownToggle>
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<DropdownToggle
|
||||
tag="span"
|
||||
className="sf2-icon-plus mobile-toolbar-icon"
|
||||
title={gettext('Add Library')}
|
||||
/>
|
||||
</MediaQuery>
|
||||
<DropdownMenu>
|
||||
<DropdownItem onClick={this.onSelectRepoToggle}>{gettext('Share existing libraries')}</DropdownItem>
|
||||
<DropdownItem onClick={this.onCreateRepoToggle}>{gettext('New Library')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isCreateRepoDialogShow && (
|
||||
<ModalPortal>
|
||||
<CreateRepoDialog
|
||||
libraryType={this.state.libraryType}
|
||||
onCreateToggle={this.onCreateRepoToggle}
|
||||
onCreateRepo={this.onCreateRepo}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isSelectRepoDialpgShow && (
|
||||
<ModalPortal>
|
||||
<ShareRepoDialog
|
||||
onRepoSelectedHandler={this.onRepoSelectedHandler}
|
||||
onShareRepoDialogClose={this.onSelectRepoToggle}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TopToolbar.propTypes = propTypes;
|
||||
|
||||
export default TopToolbar;
|
@ -719,7 +719,13 @@ a, a:hover { color: #ec8000; }
|
||||
line-height: 1.5rem;
|
||||
height: auto;
|
||||
}
|
||||
.side-nav-con .sub-nav#files-sub-nav .nav-item .nav-link {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.side-nav-con .sub-nav#files-sub-nav .sub-nav .nav-item .nav-link {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
.side-panel-slide {
|
||||
transition: all .3s ease-in-out;
|
||||
}
|
||||
|
@ -287,6 +287,7 @@ urlpatterns = [
|
||||
path('share-admin-upload-links/', react_fake_view, name="share_admin_upload_links"),
|
||||
path('shared-libs/', react_fake_view, name="shared_libs"),
|
||||
path('shared-with-ocm/', react_fake_view, name="shared_with_ocm"),
|
||||
path('libraries/', react_fake_view, name="libs"),
|
||||
path('my-libs/', react_fake_view, name="my_libs"),
|
||||
path('groups/', react_fake_view, name="groups"),
|
||||
path('group/<int:group_id>/', react_fake_view, name="group"),
|
||||
|
Loading…
Reference in New Issue
Block a user