mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 05:39:59 +00:00
[my deleted libraries] redesigned it (#6426)
* [my deleted libraries] redesigned it * moved the entrance from the right corner to the left dropdown menu of 'My Libraries' page * redesigned it: changed it from an independent page to a dialog * [my deleted libraries] improved UI details for the dialog(repo list & empty tip)
This commit is contained in:
165
frontend/src/components/dialog/my-deleted-repos.js
Normal file
165
frontend/src/components/dialog/my-deleted-repos.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { gettext, lang, trashReposExpireDays } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
import toaster from '../../components/toast';
|
||||||
|
import Loading from '../../components/loading';
|
||||||
|
import EmptyTip from '../../components/empty-tip';
|
||||||
|
|
||||||
|
import '../../css/my-deleted-repos.css';
|
||||||
|
|
||||||
|
moment.locale(lang);
|
||||||
|
|
||||||
|
class MyLibsDeleted extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
deletedRepoList: [],
|
||||||
|
isLoading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
seafileAPI.listDeletedRepo().then(res => {
|
||||||
|
this.setState({
|
||||||
|
deletedRepoList: res.data,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshDeletedRepoList = (repoID) => {
|
||||||
|
let deletedRepoList = this.state.deletedRepoList.filter(item => {
|
||||||
|
return item.repo_id !== repoID;
|
||||||
|
});
|
||||||
|
this.setState({ deletedRepoList: deletedRepoList });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { deletedRepoList: repos } = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggleDialog} className="my-deleted-repos-dialog">
|
||||||
|
<ModalHeader toggle={this.props.toggleDialog}>
|
||||||
|
{gettext('Deleted Libraries')}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody className="my-deleted-repos-container">
|
||||||
|
{this.state.isLoading && <Loading />}
|
||||||
|
{(!this.state.isLoading && repos.length === 0) &&
|
||||||
|
<EmptyTip forDialog={true} className="my-deleted-repos-empty-tip">
|
||||||
|
<h2 className="h6 font-weight-normal">{gettext('No deleted libraries')}</h2>
|
||||||
|
<p className="empty-explanation">{gettext('You have not deleted any libraries in the last {placeholder} days. A deleted library will be cleaned automatically after this period.').replace('{placeholder}', trashReposExpireDays)}</p>
|
||||||
|
</EmptyTip>
|
||||||
|
}
|
||||||
|
{repos.length !== 0 &&
|
||||||
|
<div>
|
||||||
|
<p className="tip my-deleted-repos-tip">{gettext('Tip: libraries deleted {placeholder} days ago will be cleaned automatically.').replace('{placeholder}', trashReposExpireDays)}</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="8%">{/* img*/}</th>
|
||||||
|
<th width="52%">{gettext('Name')}</th>
|
||||||
|
<th width="30%">{gettext('Deleted Time')}</th>
|
||||||
|
<th width="10%"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{repos.map((item) => {
|
||||||
|
return (
|
||||||
|
<DeletedRepoItem
|
||||||
|
key={item.repo_id}
|
||||||
|
repo={item}
|
||||||
|
refreshDeletedRepoList={this.refreshDeletedRepoList}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeletedRepoItem extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
hideRestoreMenu: true,
|
||||||
|
highlight: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
this.setState({
|
||||||
|
hideRestoreMenu: false,
|
||||||
|
highlight: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.setState({
|
||||||
|
hideRestoreMenu: true,
|
||||||
|
highlight: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
restoreDeletedRepo = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let repoID = this.props.repo.repo_id;
|
||||||
|
let repoName = this.props.repo.repo_name;
|
||||||
|
seafileAPI.restoreDeletedRepo(repoID).then(res => {
|
||||||
|
let message = gettext('Successfully restored the library {library_name}.').replace('{library_name}', repoName);
|
||||||
|
toaster.success(message);
|
||||||
|
this.props.refreshDeletedRepoList(repoID);
|
||||||
|
}).catch(error => {
|
||||||
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let localTime = moment.utc(this.props.repo.del_time).toDate();
|
||||||
|
localTime = moment(localTime).fromNow();
|
||||||
|
let iconUrl = Utils.getLibIconUrl(this.props.repo);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
className={this.state.highlight ? 'tr-highlight' : ''}
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
tabIndex="0"
|
||||||
|
onFocus={this.onMouseEnter}
|
||||||
|
>
|
||||||
|
<td className="text-center"><img src={iconUrl} alt='' width="24" /></td>
|
||||||
|
<td className="name">{this.props.repo.repo_name}</td>
|
||||||
|
<td className="update">{localTime}</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" onClick={this.restoreDeletedRepo} title={gettext('Restore')}
|
||||||
|
role="button" aria-label={gettext('Restore')}
|
||||||
|
className={`sf2-icon-reply action-icon ${this.state.highlight ? '' : 'vh'}`}>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeletedRepoItem.propTypes = {
|
||||||
|
repo: PropTypes.object.isRequired,
|
||||||
|
refreshDeletedRepoList: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
MyLibsDeleted.propTypes = {
|
||||||
|
toggleDialog: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyLibsDeleted;
|
@@ -5,13 +5,15 @@ import { mediaUrl } from '../utils/constants';
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
forDialog: PropTypes.bool,
|
forDialog: PropTypes.bool,
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
|
className: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmptyTip extends React.Component {
|
class EmptyTip extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { className } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={this.props.forDialog ? 'text-center mt-8' : 'empty-tip'}>
|
<div className={this.props.forDialog ? `${className || 'text-center mt-8'}` : 'empty-tip'}>
|
||||||
<img src={`${mediaUrl}img/no-items-tip.png`} alt="" width="100" height="100" className="no-items-img-tip" />
|
<img src={`${mediaUrl}img/no-items-tip.png`} alt="" width="100" height="100" className="no-items-img-tip" />
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
|
35
frontend/src/css/my-deleted-repos.css
Normal file
35
frontend/src/css/my-deleted-repos.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
.my-deleted-repos-dialog {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deleted-repos-container {
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deleted-repos-empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deleted-repos-empty-tip .empty-explanation {
|
||||||
|
color: #666;
|
||||||
|
font-size: .875rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.my-deleted-repos-empty-tip {
|
||||||
|
margin: 104px 0 216px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deleted-repos-empty-tip .empty-explanation {
|
||||||
|
margin: 0 7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deleted-repos-tip {
|
||||||
|
font-size: .8125rem;
|
||||||
|
color: #9c9c9c;
|
||||||
|
}
|
@@ -1,9 +1,7 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
import { navigate } from '@gatsbyjs/reach-router';
|
|
||||||
import { DropdownToggle, Dropdown, DropdownMenu, DropdownItem } from 'reactstrap';
|
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { gettext, siteRoot } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import Repo from '../../models/repo';
|
import Repo from '../../models/repo';
|
||||||
@@ -14,6 +12,7 @@ import SortOptionsDialog from '../../components/dialog/sort-options';
|
|||||||
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
|
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
|
||||||
import ModalPortal from '../../components/modal-portal';
|
import ModalPortal from '../../components/modal-portal';
|
||||||
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
||||||
|
import DeletedReposDialog from '../../components/dialog/my-deleted-repos';
|
||||||
|
|
||||||
class MyLibraries extends Component {
|
class MyLibraries extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -22,8 +21,8 @@ class MyLibraries extends Component {
|
|||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
repoList: [],
|
repoList: [],
|
||||||
|
isDeletedReposDialogOpen: false,
|
||||||
isCreateRepoDialogOpen: false,
|
isCreateRepoDialogOpen: false,
|
||||||
isDropdownMenuOpen: false,
|
|
||||||
isSortOptionsDialogOpen: false,
|
isSortOptionsDialogOpen: false,
|
||||||
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,24 +128,13 @@ class MyLibraries extends Component {
|
|||||||
this.setState({ isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen });
|
this.setState({ isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen });
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleDropdownMenu = () => {
|
toggleDeletedReposDialog = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isDropdownMenuOpen: !this.state.isDropdownMenuOpen
|
isDeletedReposDialogOpen: !this.state.isDeletedReposDialogOpen
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
visitDeleted = () => {
|
|
||||||
navigate(`${siteRoot}my-libs/deleted/`);
|
|
||||||
};
|
|
||||||
|
|
||||||
visitDeletedviaKey = (e) => {
|
|
||||||
if (e.key == 'Enter' || e.key == 'Space') {
|
|
||||||
this.visiteDeleted();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isDropdownMenuOpen } = this.state;
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="main-panel-center flex-row">
|
<div className="main-panel-center flex-row">
|
||||||
@@ -155,25 +143,14 @@ class MyLibraries extends Component {
|
|||||||
<h3 className="sf-heading m-0">
|
<h3 className="sf-heading m-0">
|
||||||
{gettext('My Libraries')}
|
{gettext('My Libraries')}
|
||||||
<SingleDropdownToolbar
|
<SingleDropdownToolbar
|
||||||
opList={[{ 'text': gettext('New Library'), 'onClick': this.toggleCreateRepoDialog }]}
|
opList={[
|
||||||
|
{ 'text': gettext('New Library'), 'onClick': this.toggleCreateRepoDialog },
|
||||||
|
{ 'text': gettext('Deleted Libraries'), 'onClick': this.toggleDeletedReposDialog }
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||||
<Dropdown isOpen={isDropdownMenuOpen} toggle={this.toggleDropdownMenu}>
|
|
||||||
<DropdownToggle
|
|
||||||
tag="i"
|
|
||||||
className={'cur-view-path-btn sf3-font-more sf3-font ml-2'}
|
|
||||||
data-toggle="dropdown"
|
|
||||||
title={gettext('More operations')}
|
|
||||||
aria-label={gettext('More operations')}
|
|
||||||
aria-expanded={isDropdownMenuOpen}
|
|
||||||
>
|
|
||||||
</DropdownToggle>
|
|
||||||
<DropdownMenu right={true}>
|
|
||||||
<DropdownItem onClick={this.visitDeleted} onKeyDown={this.visitDeletedviaKey}>{gettext('Deleted Libraries')}</DropdownItem>
|
|
||||||
</DropdownMenu>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content">
|
||||||
@@ -211,6 +188,13 @@ class MyLibraries extends Component {
|
|||||||
/>
|
/>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
)}
|
)}
|
||||||
|
{this.state.isDeletedReposDialogOpen && (
|
||||||
|
<ModalPortal>
|
||||||
|
<DeletedReposDialog
|
||||||
|
toggleDialog={this.toggleDeletedReposDialog}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user