mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-06 17:43:46 +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 OCMRepoDir from './pages/share-with-ocm/remote-dir-view';
|
||||||
import MyLibraries from './pages/my-libs/my-libs';
|
import MyLibraries from './pages/my-libs/my-libs';
|
||||||
import MyLibDeleted from './pages/my-libs/my-libs-deleted';
|
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 LibContentView from './pages/lib-content-view/lib-content-view';
|
||||||
import Group from './pages/groups/group-view';
|
import Group from './pages/groups/group-view';
|
||||||
import Groups from './pages/groups/groups-view';
|
import Groups from './pages/groups/groups-view';
|
||||||
import InvitationsView from './pages/invitations/invitations-view';
|
import InvitationsView from './pages/invitations/invitations-view';
|
||||||
import Wikis from './pages/wikis/wikis';
|
import Wikis from './pages/wikis/wikis';
|
||||||
|
import Libraries from './pages/libraries';
|
||||||
import MainContentWrapper from './components/main-content-wrapper';
|
import MainContentWrapper from './components/main-content-wrapper';
|
||||||
|
|
||||||
import './css/layout.css';
|
import './css/layout.css';
|
||||||
@ -201,10 +202,6 @@ class App extends Component {
|
|||||||
render() {
|
render() {
|
||||||
let { currentTab, isSidePanelClosed } = this.state;
|
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 (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<SystemNotification />
|
<SystemNotification />
|
||||||
@ -212,7 +209,8 @@ class App extends Component {
|
|||||||
<SidePanel isSidePanelClosed={this.state.isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} tabItemClick={this.tabItemClick} />
|
<SidePanel isSidePanelClosed={this.state.isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} tabItemClick={this.tabItemClick} />
|
||||||
<MainPanel>
|
<MainPanel>
|
||||||
<Router className="reach-router">
|
<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} />
|
<FilesActivitiesWrapper path={siteRoot + 'dashboard'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<MyFileActivitiesWrapper path={siteRoot + 'my-activities'} 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} />
|
<StarredWrapper path={siteRoot + 'starred'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
|
@ -39,7 +39,8 @@ class CreateGroupDialog extends React.Component {
|
|||||||
if (name) {
|
if (name) {
|
||||||
let that = this;
|
let that = this;
|
||||||
seafileAPI.createGroup(name).then((res)=> {
|
seafileAPI.createGroup(name).then((res)=> {
|
||||||
that.props.onCreateGroup();
|
that.props.onCreateGroup(res.data);
|
||||||
|
this.props.toggleDialog();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
let errorMsg = Utils.getErrorMsg(error);
|
let errorMsg = Utils.getErrorMsg(error);
|
||||||
this.setState({errorMsg: errorMsg});
|
this.setState({errorMsg: errorMsg});
|
||||||
@ -63,8 +64,8 @@ class CreateGroupDialog extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return(
|
return(
|
||||||
<Modal isOpen={this.props.showAddGroupModal} toggle={this.props.toggleAddGroupModal} autoFocus={false}>
|
<Modal isOpen={true} toggle={this.props.toggleDialog} autoFocus={false}>
|
||||||
<ModalHeader toggle={this.props.toggleAddGroupModal}>{gettext('New Group')}</ModalHeader>
|
<ModalHeader toggle={this.props.toggleDialog}>{gettext('New Group')}</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<label htmlFor="groupName">{gettext('Name')}</label>
|
<label htmlFor="groupName">{gettext('Name')}</label>
|
||||||
<Input
|
<Input
|
||||||
@ -78,7 +79,7 @@ class CreateGroupDialog extends React.Component {
|
|||||||
<span className="error">{this.state.errorMsg}</span>
|
<span className="error">{this.state.errorMsg}</span>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<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>
|
<Button color="primary" onClick={this.handleSubmitGroup} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -87,9 +88,8 @@ class CreateGroupDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CreateGroupDialogPropTypes = {
|
const CreateGroupDialogPropTypes = {
|
||||||
toggleAddGroupModal: PropTypes.func.isRequired,
|
toggleDialog: PropTypes.func.isRequired,
|
||||||
onCreateGroup: PropTypes.func.isRequired,
|
onCreateGroup: PropTypes.func.isRequired
|
||||||
showAddGroupModal: PropTypes.bool.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CreateGroupDialog.propTypes = CreateGroupDialogPropTypes;
|
CreateGroupDialog.propTypes = CreateGroupDialogPropTypes;
|
||||||
|
@ -18,6 +18,7 @@ class MainSideNav extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
FilesNavUnfolded: false,
|
||||||
groupsExtended: false,
|
groupsExtended: false,
|
||||||
sharedExtended: false,
|
sharedExtended: false,
|
||||||
closeSideBar: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() {
|
render() {
|
||||||
let showActivity = isDocs || isPro || !isDBSqlite3;
|
let showActivity = isDocs || isPro || !isDBSqlite3;
|
||||||
|
const { FilesNavUnfolded } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="side-nav">
|
<div className="side-nav">
|
||||||
<div className="side-nav-con">
|
<div className="side-nav-con">
|
||||||
<h3 className="sf-heading">{gettext('Files')}</h3>
|
|
||||||
<ul className="nav nav-pills flex-column nav-container">
|
<ul className="nav nav-pills flex-column nav-container">
|
||||||
{canAddRepo && (
|
<li className="nav-item flex-column" id="files">
|
||||||
<li className="nav-item">
|
<Link to={ siteRoot + 'libraries/' } className={`nav-link ellipsis ${this.getActiveClass('libraries')}`} title={gettext('Files')} onClick={(e) => this.tabItemClick(e, 'libraries')}>
|
||||||
<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="sf3-font-files sf3-font" aria-hidden="true"></span>
|
||||||
<span className="sf2-icon-user" aria-hidden="true"></span>
|
<span className="nav-text">{gettext('Files')}</span>
|
||||||
<span className="nav-text">{gettext('My Libraries')}</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>
|
|
||||||
</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>
|
|
||||||
</Link>
|
</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>
|
</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">
|
<li className="nav-item">
|
||||||
<Link className={`nav-link ellipsis ${this.getActiveClass('starred')}`} to={siteRoot + 'starred/'} title={gettext('Favorites')} onClick={(e) => this.tabItemClick(e, 'starred')}>
|
<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>
|
<span className="sf2-icon-star" aria-hidden="true"></span>
|
||||||
|
@ -29,7 +29,6 @@ const propTypes = {
|
|||||||
onFreezedItem: PropTypes.func.isRequired,
|
onFreezedItem: PropTypes.func.isRequired,
|
||||||
onUnfreezedItem: PropTypes.func.isRequired,
|
onUnfreezedItem: PropTypes.func.isRequired,
|
||||||
onItemUnshare: PropTypes.func.isRequired,
|
onItemUnshare: PropTypes.func.isRequired,
|
||||||
onItemDetails: PropTypes.func,
|
|
||||||
onItemRename: PropTypes.func,
|
onItemRename: PropTypes.func,
|
||||||
onItemDelete: PropTypes.func,
|
onItemDelete: PropTypes.func,
|
||||||
onMonitorRepo: PropTypes.func
|
onMonitorRepo: PropTypes.func
|
||||||
@ -165,9 +164,6 @@ class SharedRepoListItem extends React.Component {
|
|||||||
case 'Folder Permission':
|
case 'Folder Permission':
|
||||||
this.onItemFolderPermissionToggle();
|
this.onItemFolderPermissionToggle();
|
||||||
break;
|
break;
|
||||||
case 'Details':
|
|
||||||
this.onItemDetails();
|
|
||||||
break;
|
|
||||||
case 'Share':
|
case 'Share':
|
||||||
this.onItemShare();
|
this.onItemShare();
|
||||||
break;
|
break;
|
||||||
@ -251,10 +247,6 @@ class SharedRepoListItem extends React.Component {
|
|||||||
this.setState({isHistorySettingDialogShow: !this.state.isHistorySettingDialogShow});
|
this.setState({isHistorySettingDialogShow: !this.state.isHistorySettingDialogShow});
|
||||||
};
|
};
|
||||||
|
|
||||||
onItemDetails = () => {
|
|
||||||
this.props.onItemDetails(this.props.repo);
|
|
||||||
};
|
|
||||||
|
|
||||||
onItemShare = (e) => {
|
onItemShare = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({isShowSharedDialog: true});
|
this.setState({isShowSharedDialog: true});
|
||||||
@ -338,9 +330,6 @@ class SharedRepoListItem extends React.Component {
|
|||||||
case 'Folder Permission':
|
case 'Folder Permission':
|
||||||
translateResult = gettext('Folder Permission');
|
translateResult = gettext('Folder Permission');
|
||||||
break;
|
break;
|
||||||
case 'Details':
|
|
||||||
translateResult = gettext('Details');
|
|
||||||
break;
|
|
||||||
case 'Unshare':
|
case 'Unshare':
|
||||||
translateResult = gettext('Unshare');
|
translateResult = gettext('Unshare');
|
||||||
break;
|
break;
|
||||||
@ -423,7 +412,7 @@ class SharedRepoListItem extends React.Component {
|
|||||||
const monitorOp = repo.monitored ? 'Unwatch File Changes' : 'Watch File Changes';
|
const monitorOp = repo.monitored ? 'Unwatch File Changes' : 'Watch File Changes';
|
||||||
operations.push(monitorOp);
|
operations.push(monitorOp);
|
||||||
}
|
}
|
||||||
operations.push('Divider', 'History Setting', 'Details');
|
operations.push('Divider', 'History Setting');
|
||||||
if (Utils.isDesktop()) {
|
if (Utils.isDesktop()) {
|
||||||
operations.push('Advanced');
|
operations.push('Advanced');
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ const propTypes = {
|
|||||||
repoList: PropTypes.array.isRequired,
|
repoList: PropTypes.array.isRequired,
|
||||||
onItemUnshare: PropTypes.func.isRequired,
|
onItemUnshare: PropTypes.func.isRequired,
|
||||||
onItemDelete: PropTypes.func,
|
onItemDelete: PropTypes.func,
|
||||||
onItemDetails: PropTypes.func,
|
|
||||||
onItemRename: PropTypes.func,
|
onItemRename: PropTypes.func,
|
||||||
hasNextPage: PropTypes.bool,
|
hasNextPage: PropTypes.bool,
|
||||||
onMonitorRepo: PropTypes.func,
|
onMonitorRepo: PropTypes.func,
|
||||||
@ -98,7 +97,6 @@ class SharedRepoListView extends React.Component {
|
|||||||
onUnfreezedItem={this.onUnfreezedItem}
|
onUnfreezedItem={this.onUnfreezedItem}
|
||||||
onItemUnshare={this.props.onItemUnshare}
|
onItemUnshare={this.props.onItemUnshare}
|
||||||
onItemDelete={this.props.onItemDelete}
|
onItemDelete={this.props.onItemDelete}
|
||||||
onItemDetails={this.props.onItemDetails}
|
|
||||||
onItemRename={this.props.onItemRename}
|
onItemRename={this.props.onItemRename}
|
||||||
onMonitorRepo={this.props.onMonitorRepo}
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
/>
|
/>
|
||||||
@ -109,12 +107,11 @@ class SharedRepoListView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderPCUI = () => {
|
renderPCUI = () => {
|
||||||
let isShowTableThread = this.props.isShowTableThread !== undefined ? this.props.isShowTableThread : true;
|
const { theadHidden = false } = this.props;
|
||||||
|
|
||||||
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
|
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className={isShowTableThread ? '' : 'table-thead-hidden'}>
|
<table className={theadHidden ? 'table-thead-hidden' : ''}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="4%"></th>
|
<th width="4%"></th>
|
||||||
|
@ -1,44 +1,47 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MediaQuery from 'react-responsive';
|
import { Utils } from '../../utils/utils';
|
||||||
import CommonToolbar from './common-toolbar';
|
import { gettext } from '../../utils/constants';
|
||||||
import { Button } from 'reactstrap';
|
import CreateGroupDialog from '../../components/dialog/create-group-dialog';
|
||||||
import { gettext, canAddGroup } from '../../utils/constants';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
searchPlaceholder: PropTypes.string,
|
onCreateGroup: PropTypes.func.isRequired
|
||||||
onShowSidePanel: PropTypes.func.isRequired,
|
|
||||||
onSearchedClick: PropTypes.func.isRequired,
|
|
||||||
toggleAddGroupModal: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GroupsToolbar extends React.Component {
|
class GroupsToolbar extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isDialogOpen: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleDialog = () => {
|
||||||
|
this.setState({
|
||||||
|
isDialogOpen: !this.state.isDialogOpen
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { onShowSidePanel, onSearchedClick } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<div className="main-panel-north border-left-show">
|
<>
|
||||||
<div className="cur-view-toolbar">
|
{Utils.isDesktop() ? (
|
||||||
<span title="Side Nav Menu" onClick={onShowSidePanel} className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none"></span>
|
<div className="operation">
|
||||||
{canAddGroup && (
|
<button className="btn btn-secondary operation-item" onClick={this.toggleDialog}>
|
||||||
<div className="operation">
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
|
||||||
<MediaQuery query="(min-width: 768px)">
|
</button>
|
||||||
<Button color="btn btn-secondary operation-item" onClick={this.props.toggleAddGroupModal}>
|
</div>
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
|
) : (
|
||||||
</Button>
|
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.toggleDialog}></span>
|
||||||
</MediaQuery>
|
)}
|
||||||
<MediaQuery query="(max-width: 767.8px)">
|
{this.state.isDialogOpen &&
|
||||||
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.props.toggleAddGroupModal}></span>
|
<CreateGroupDialog
|
||||||
</MediaQuery>
|
toggleDialog={this.toggleDialog}
|
||||||
</div>
|
onCreateGroup={this.props.onCreateGroup}
|
||||||
)}
|
/>
|
||||||
</div>
|
}
|
||||||
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick}/>
|
</>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
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 { Link, navigate } from '@gatsbyjs/reach-router';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
import { siteRoot, gettext } from '../../utils/constants';
|
import { siteRoot, gettext } from '../../utils/constants';
|
||||||
import ModalPortal from '../modal-portal';
|
import ModalPortal from '../modal-portal';
|
||||||
import CreateRepoDialog from '../dialog/create-repo-dialog';
|
import CreateRepoDialog from '../dialog/create-repo-dialog';
|
||||||
import { DropdownToggle, Dropdown, DropdownMenu, DropdownItem } from 'reactstrap';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
libraryType: PropTypes.string.isRequired,
|
|
||||||
onCreateRepo: PropTypes.func.isRequired,
|
onCreateRepo: PropTypes.func.isRequired,
|
||||||
onShowSidePanel: PropTypes.func.isRequired,
|
moreShown: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
class RepoViewToolbar extends React.Component {
|
class MyLibsToolbar extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isCreateRepoDialogShow: false,
|
isCreateRepoDialogOpen: false,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -29,7 +28,7 @@ class RepoViewToolbar extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onCreateToggle = () => {
|
onCreateToggle = () => {
|
||||||
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
|
this.setState({isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen});
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleMore = () => {
|
toggleMore = () => {
|
||||||
@ -49,15 +48,15 @@ class RepoViewToolbar extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { moreShown = false } = this.props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="cur-view-toolbar">
|
{Utils.isDesktop() ? (
|
||||||
<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">
|
||||||
{Utils.isDesktop() ? (
|
<button className="btn btn-secondary operation-item" title={gettext('New Library')} onClick={this.onCreateToggle}>
|
||||||
<div className="operation">
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Library')}
|
||||||
<button className="btn btn-secondary operation-item" title={gettext('New Library')} onClick={this.onCreateToggle}>
|
</button>
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Library')}
|
{moreShown &&
|
||||||
</button>
|
|
||||||
<Dropdown isOpen={this.state.isOpen} toggle={this.toggleMore}>
|
<Dropdown isOpen={this.state.isOpen} toggle={this.toggleMore}>
|
||||||
<DropdownToggle className='btn btn-secondary operation-item' onKeyDown={this.onDropdownToggleKeyDown}>
|
<DropdownToggle className='btn btn-secondary operation-item' onKeyDown={this.onDropdownToggleKeyDown}>
|
||||||
{gettext('More')}
|
{gettext('More')}
|
||||||
@ -68,15 +67,15 @@ class RepoViewToolbar extends React.Component {
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
}
|
||||||
) : (
|
</div>
|
||||||
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Library')} onClick={this.onCreateToggle}></span>
|
) : (
|
||||||
)}
|
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Library')} onClick={this.onCreateToggle}></span>
|
||||||
</div>
|
)}
|
||||||
{this.state.isCreateRepoDialogShow && (
|
{this.state.isCreateRepoDialogOpen && (
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<CreateRepoDialog
|
<CreateRepoDialog
|
||||||
libraryType={this.props.libraryType}
|
libraryType='mine'
|
||||||
onCreateRepo={this.onCreateRepo}
|
onCreateRepo={this.onCreateRepo}
|
||||||
onCreateToggle={this.onCreateToggle}
|
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 ManageMembersDialog from '../../components/dialog/manage-members-dialog';
|
||||||
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
||||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
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 SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
|
|
||||||
import '../../css/group-view.css';
|
import '../../css/group-view.css';
|
||||||
@ -65,7 +64,6 @@ class GroupView extends React.Component {
|
|||||||
showImportMembersDialog: false,
|
showImportMembersDialog: false,
|
||||||
showManageMembersDialog: false,
|
showManageMembersDialog: false,
|
||||||
groupMembers: [],
|
groupMembers: [],
|
||||||
isShowDetails: false,
|
|
||||||
isLeaveGroupDialogOpen: 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) => {
|
sortItems = (sortBy, sortOrder) => {
|
||||||
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||||
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||||
@ -568,18 +555,12 @@ class GroupView extends React.Component {
|
|||||||
sortItems={this.sortItems}
|
sortItems={this.sortItems}
|
||||||
onItemUnshare={this.onItemUnshare}
|
onItemUnshare={this.onItemUnshare}
|
||||||
onItemDelete={this.onItemDelete}
|
onItemDelete={this.onItemDelete}
|
||||||
onItemDetails={this.onItemDetails}
|
|
||||||
onItemRename={this.onItemRename}
|
onItemRename={this.onItemRename}
|
||||||
onMonitorRepo={this.onMonitorRepo}
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.isShowDetails && (
|
|
||||||
<div className="cur-view-detail">
|
|
||||||
<LibDetail currentRepo={this.state.currentRepo} closeDetails={this.closeDetails}/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && (
|
{this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && (
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
|
@ -1,119 +1,18 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
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 { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import Group from '../../models/group';
|
import Group from '../../models/group';
|
||||||
import Repo from '../../models/repo';
|
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 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 EmptyTip from '../../components/empty-tip';
|
||||||
|
import GroupItem from './group-item';
|
||||||
|
|
||||||
import '../../css/groups.css';
|
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 {
|
class GroupsView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -121,10 +20,7 @@ class GroupsView extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
groupList: [],
|
groupList: []
|
||||||
showAddGroupModal: false,
|
|
||||||
isShowDetails: false,
|
|
||||||
currentRepo: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +29,9 @@ class GroupsView extends React.Component {
|
|||||||
// `{'with_repos': 1}`: list repos of every group
|
// `{'with_repos': 1}`: list repos of every group
|
||||||
let groupList = res.data.map(item => {
|
let groupList = res.data.map(item => {
|
||||||
let group = new Group(item);
|
let group = new Group(item);
|
||||||
|
group.repos = item.repos.map(item => {
|
||||||
|
return new Repo(item);
|
||||||
|
});
|
||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
this.setState({
|
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({
|
this.setState({
|
||||||
showAddGroupModal: !this.state.showAddGroupModal
|
groupList: newList
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onCreateGroup = () => {
|
|
||||||
this.setState({
|
|
||||||
showAddGroupModal: false,
|
|
||||||
isLoading: true,
|
|
||||||
groupList: [],
|
|
||||||
});
|
|
||||||
this.listGroups();
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.listGroups();
|
this.listGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
onItemDetails = (repo) => {
|
updateGroup = (group) => {
|
||||||
|
const { groupList } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
isShowDetails: true,
|
groupList: groupList.map((item) => {
|
||||||
currentRepo: repo,
|
if (item.id == group.id) {
|
||||||
|
item = group;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
closeDetails = () => {
|
|
||||||
this.setState({isShowDetails: false});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const emptyTip = (
|
const emptyTip = (
|
||||||
<EmptyTip>
|
<EmptyTip>
|
||||||
@ -192,11 +86,12 @@ class GroupsView extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<GroupsToolbar
|
<TopToolbar
|
||||||
onShowSidePanel={this.props.onShowSidePanel}
|
onShowSidePanel={this.props.onShowSidePanel}
|
||||||
onSearchedClick={this.props.onSearchedClick}
|
onSearchedClick={this.props.onSearchedClick}
|
||||||
toggleAddGroupModal={this.toggleAddGroupModal}
|
>
|
||||||
/>
|
{canAddGroup && <GroupsToolbar onCreateGroup={this.onCreateGroup} />}
|
||||||
|
</TopToolbar>
|
||||||
<div className="main-panel-center flex-row">
|
<div className="main-panel-center flex-row">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path">
|
<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.errorMsg && this.state.groupList.length == 0) && emptyTip}
|
||||||
{!this.state.isLoading && this.state.groupList.map((group, index) => {
|
{!this.state.isLoading && this.state.groupList.map((group, index) => {
|
||||||
return (
|
return (
|
||||||
<RepoListViewPanel
|
<GroupItem
|
||||||
key={index}
|
key={index}
|
||||||
group={group}
|
group={group}
|
||||||
onItemDetails={this.onItemDetails}
|
updateGroup={this.updateGroup}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.isShowDetails && (
|
|
||||||
<div className="cur-view-detail">
|
|
||||||
<LibDetail currentRepo={this.state.currentRepo} closeDetails={this.closeDetails}/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{ this.state.showAddGroupModal &&
|
|
||||||
<CreateGroupDialog
|
|
||||||
toggleAddGroupModal={this.toggleAddGroupModal}
|
|
||||||
onCreateGroup={this.onCreateGroup}
|
|
||||||
showAddGroupModal={this.state.showAddGroupModal}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Fragment>
|
</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 Repo from '../../models/repo';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import EmptyTip from '../../components/empty-tip';
|
import EmptyTip from '../../components/empty-tip';
|
||||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||||
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
|
import MyLibsToolbar from '../../components/toolbar/my-libs-toolbar';
|
||||||
import LibDetail from '../../components/dirent-detail/lib-details';
|
|
||||||
import MylibRepoListView from './mylib-repo-list-view';
|
import MylibRepoListView from './mylib-repo-list-view';
|
||||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
onShowSidePanel: PropTypes.func.isRequired,
|
onShowSidePanel: PropTypes.func.isRequired,
|
||||||
@ -27,9 +25,7 @@ class MyLibraries extends Component {
|
|||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
repoList: [],
|
repoList: [],
|
||||||
isShowDetails: false,
|
|
||||||
isSortOptionsDialogOpen: false,
|
isSortOptionsDialogOpen: false,
|
||||||
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
|
|
||||||
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
|
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'
|
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
||||||
};
|
};
|
||||||
@ -129,37 +125,15 @@ class MyLibraries extends Component {
|
|||||||
this.setState({repoList: repoList});
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="main-panel-north border-left-show">
|
<TopToolbar
|
||||||
<RepoViewToolbar onShowSidePanel={this.props.onShowSidePanel} onCreateRepo={this.onCreateRepo} libraryType={'mine'}/>
|
onShowSidePanel={this.props.onShowSidePanel}
|
||||||
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
|
onSearchedClick={this.props.onSearchedClick}
|
||||||
</div>
|
>
|
||||||
|
<MyLibsToolbar onCreateRepo={this.onCreateRepo} moreShown={true} />
|
||||||
|
</TopToolbar>
|
||||||
<div className="main-panel-center flex-row">
|
<div className="main-panel-center flex-row">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path">
|
<div className="cur-view-path">
|
||||||
@ -179,17 +153,11 @@ class MyLibraries extends Component {
|
|||||||
onDeleteRepo={this.onDeleteRepo}
|
onDeleteRepo={this.onDeleteRepo}
|
||||||
onTransferRepo={this.onTransferRepo}
|
onTransferRepo={this.onTransferRepo}
|
||||||
onMonitorRepo={this.onMonitorRepo}
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
onRepoClick={this.onRepoClick}
|
|
||||||
sortRepoList={this.sortRepoList}
|
sortRepoList={this.sortRepoList}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen &&
|
|
||||||
<GuideForNewDialog
|
|
||||||
toggleDialog={this.toggleGuideForNewDialog}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{this.state.isSortOptionsDialogOpen &&
|
{this.state.isSortOptionsDialogOpen &&
|
||||||
<SortOptionsDialog
|
<SortOptionsDialog
|
||||||
toggleDialog={this.toggleSortOptionsDialog}
|
toggleDialog={this.toggleSortOptionsDialog}
|
||||||
@ -198,14 +166,6 @@ class MyLibraries extends Component {
|
|||||||
sortItems={this.sortRepoList}
|
sortItems={this.sortRepoList}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{this.state.isShowDetails && (
|
|
||||||
<div className="cur-view-detail">
|
|
||||||
<LibDetail
|
|
||||||
currentRepo={this.state.currentRepo}
|
|
||||||
closeDetails={this.closeDetails}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
@ -32,7 +32,6 @@ const propTypes = {
|
|||||||
onRenameRepo: PropTypes.func.isRequired,
|
onRenameRepo: PropTypes.func.isRequired,
|
||||||
onDeleteRepo: PropTypes.func.isRequired,
|
onDeleteRepo: PropTypes.func.isRequired,
|
||||||
onTransferRepo: PropTypes.func.isRequired,
|
onTransferRepo: PropTypes.func.isRequired,
|
||||||
onRepoClick: PropTypes.func.isRequired,
|
|
||||||
onMonitorRepo: 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) => {
|
onToggleStarRepo = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const repoName = this.props.repo.repo_name;
|
const repoName = this.props.repo.repo_name;
|
||||||
@ -328,7 +323,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
let iconTitle = Utils.getLibIconTitle(repo);
|
let iconTitle = Utils.getLibIconTitle(repo);
|
||||||
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||||
return (
|
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">
|
<td className="text-center">
|
||||||
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
<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>
|
<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)}/`;
|
let repoURL = this.repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||||
|
|
||||||
return (
|
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}><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||||
<td onClick={this.visitRepo}>
|
<td onClick={this.visitRepo}>
|
||||||
{this.state.isRenaming && (
|
{this.state.isRenaming && (
|
||||||
|
@ -13,8 +13,8 @@ const propTypes = {
|
|||||||
onRenameRepo: PropTypes.func.isRequired,
|
onRenameRepo: PropTypes.func.isRequired,
|
||||||
onDeleteRepo: PropTypes.func.isRequired,
|
onDeleteRepo: PropTypes.func.isRequired,
|
||||||
onTransferRepo: PropTypes.func.isRequired,
|
onTransferRepo: PropTypes.func.isRequired,
|
||||||
onRepoClick: PropTypes.func.isRequired,
|
|
||||||
onMonitorRepo: PropTypes.func.isRequired,
|
onMonitorRepo: PropTypes.func.isRequired,
|
||||||
|
theadHidden : PropTypes.bool, // for 'My Libraries' in 'Files' page
|
||||||
};
|
};
|
||||||
|
|
||||||
class MylibRepoListView extends React.Component {
|
class MylibRepoListView extends React.Component {
|
||||||
@ -70,7 +70,6 @@ class MylibRepoListView extends React.Component {
|
|||||||
onDeleteRepo={this.props.onDeleteRepo}
|
onDeleteRepo={this.props.onDeleteRepo}
|
||||||
onTransferRepo={this.props.onTransferRepo}
|
onTransferRepo={this.props.onTransferRepo}
|
||||||
onMonitorRepo={this.props.onMonitorRepo}
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
onRepoClick={this.props.onRepoClick}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -79,10 +78,11 @@ class MylibRepoListView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderPCUI = () => {
|
renderPCUI = () => {
|
||||||
|
const { theadHidden } = this.props;
|
||||||
const showStorageBackend = storages.length > 0;
|
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>;
|
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
||||||
return (
|
return (
|
||||||
<table>
|
<table className={theadHidden ? 'table-thead-hidden' : ''}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="4%"></th>
|
<th width="4%"></th>
|
||||||
|
@ -54,7 +54,7 @@ class Content extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, errorMsg, items, sortBy, sortOrder } = this.props;
|
const { loading, errorMsg, items, sortBy, sortOrder, theadHidden } = this.props;
|
||||||
|
|
||||||
const emptyTip = (
|
const emptyTip = (
|
||||||
<EmptyTip>
|
<EmptyTip>
|
||||||
@ -90,7 +90,7 @@ class Content extends Component {
|
|||||||
|
|
||||||
const isDesktop = Utils.isDesktop();
|
const isDesktop = Utils.isDesktop();
|
||||||
const table = (
|
const table = (
|
||||||
<table className={isDesktop ? '' : 'table-thead-hidden'}>
|
<table className={(isDesktop && !theadHidden)? '' : 'table-thead-hidden'}>
|
||||||
{isDesktop ? desktopThead : <LibsMobileThead />}
|
{isDesktop ? desktopThead : <LibsMobileThead />}
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
@ -113,6 +113,7 @@ class Content extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Content.propTypes = {
|
Content.propTypes = {
|
||||||
|
theadHidden: PropTypes.bool.isRequired,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
errorMsg: PropTypes.string.isRequired,
|
errorMsg: PropTypes.string.isRequired,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
@ -447,28 +448,55 @@ class SharedLibraries extends Component {
|
|||||||
this.setState({items: items});
|
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() {
|
render() {
|
||||||
|
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="main-panel-center">
|
{inAllLibs ? (
|
||||||
<div className="cur-view-container">
|
<>
|
||||||
<div className="cur-view-path">
|
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
|
||||||
<h3 className="sf-heading m-0">{gettext('Shared with me')}</h3>
|
<h4 className="sf-heading m-0">{gettext('Shared with me')}</h4>
|
||||||
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
{this.renderSortIconInMobile()}
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
{this.renderContent()}
|
||||||
<Content
|
</>
|
||||||
loading={this.state.loading}
|
) : (
|
||||||
errorMsg={this.state.errorMsg}
|
<div className="main-panel-center">
|
||||||
items={this.state.items}
|
<div className="cur-view-container">
|
||||||
sortBy={this.state.sortBy}
|
<div className="cur-view-path">
|
||||||
sortOrder={this.state.sortOrder}
|
<h3 className="sf-heading m-0">{gettext('Shared with me')}</h3>
|
||||||
sortItems={this.sortItems}
|
{this.renderSortIconInMobile()}
|
||||||
onMonitorRepo={this.onMonitorRepo}
|
</div>
|
||||||
/>
|
<div className="cur-view-content">
|
||||||
|
{this.renderContent()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
{this.state.isSortOptionsDialogOpen &&
|
{this.state.isSortOptionsDialogOpen &&
|
||||||
<SortOptionsDialog
|
<SortOptionsDialog
|
||||||
toggleDialog={this.toggleSortOptionsDialog}
|
toggleDialog={this.toggleSortOptionsDialog}
|
||||||
@ -482,4 +510,8 @@ class SharedLibraries extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SharedLibraries.propTypes = {
|
||||||
|
inAllLibs: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
export default SharedLibraries;
|
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;
|
line-height: 1.5rem;
|
||||||
height: auto;
|
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 {
|
.side-panel-slide {
|
||||||
transition: all .3s ease-in-out;
|
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('share-admin-upload-links/', react_fake_view, name="share_admin_upload_links"),
|
||||||
path('shared-libs/', react_fake_view, name="shared_libs"),
|
path('shared-libs/', react_fake_view, name="shared_libs"),
|
||||||
path('shared-with-ocm/', react_fake_view, name="shared_with_ocm"),
|
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('my-libs/', react_fake_view, name="my_libs"),
|
||||||
path('groups/', react_fake_view, name="groups"),
|
path('groups/', react_fake_view, name="groups"),
|
||||||
path('group/<int:group_id>/', react_fake_view, name="group"),
|
path('group/<int:group_id>/', react_fake_view, name="group"),
|
||||||
|
Loading…
Reference in New Issue
Block a user