diff --git a/frontend/src/css/my-deleted-repos.css b/frontend/src/components/dialog/my-deleted-repos-dialog/index.css similarity index 63% rename from frontend/src/css/my-deleted-repos.css rename to frontend/src/components/dialog/my-deleted-repos-dialog/index.css index 53eb9c26fb..c1724f11a4 100644 --- a/frontend/src/css/my-deleted-repos.css +++ b/frontend/src/components/dialog/my-deleted-repos-dialog/index.css @@ -1,11 +1,15 @@ .my-deleted-repos-dialog { max-width: 720px; + height: calc(100% - 56px); + overflow: hidden; } -.my-deleted-repos-container { - min-height: 200px; - max-height: 500px; - overflow: auto; +.my-deleted-repos-dialog .modal-content { + height: 100%; +} + +.my-deleted-repos-dialog .my-deleted-repos-container { + overflow-y: scroll; } .my-deleted-repos-empty-tip { @@ -25,5 +29,5 @@ .my-deleted-repos-tip { font-size: .8125rem; - color: #9c9c9c; + color: #666; } diff --git a/frontend/src/components/dialog/my-deleted-repos-dialog/index.js b/frontend/src/components/dialog/my-deleted-repos-dialog/index.js new file mode 100644 index 0000000000..56fcaff43c --- /dev/null +++ b/frontend/src/components/dialog/my-deleted-repos-dialog/index.js @@ -0,0 +1,65 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +import { gettext, trashReposExpireDays } from '../../../utils/constants'; +import { seafileAPI } from '../../../utils/seafile-api'; +import { Utils } from '../../../utils/utils'; +import toaster from '../../toast'; +import Loading from '../../loading'; +import EmptyTip from '../../empty-tip'; +import Repos from './repos'; + +import './index.css'; + +const MyDeletedReposDialog = ({ toggleDialog }) => { + + const [isLoading, setLoading] = useState(true); + const [deletedRepoList, setDeletedRepoList] = useState([]); + + useEffect(() => { + seafileAPI.listDeletedRepo().then(res => { + setDeletedRepoList(res.data); + setLoading(false); + }).catch(error => { + const errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + }, []); + + const filterRestoredRepo = useCallback((restoredRepoID) => { + const newDeletedRepoList = deletedRepoList.filter(item => { + return item.repo_id !== restoredRepoID; + }); + setDeletedRepoList(newDeletedRepoList); + }, [deletedRepoList]); + + return ( + + {gettext('Deleted Libraries')} + + {isLoading ? ( + + ) : ( + <> + {deletedRepoList.length === 0 ? ( + + ) : ( + + )} + + )} + + + ); + +}; + +MyDeletedReposDialog.propTypes = { + toggleDialog: PropTypes.func.isRequired +}; + +export default MyDeletedReposDialog; diff --git a/frontend/src/components/dialog/my-deleted-repos-dialog/repo-item.js b/frontend/src/components/dialog/my-deleted-repos-dialog/repo-item.js new file mode 100644 index 0000000000..ee635bc579 --- /dev/null +++ b/frontend/src/components/dialog/my-deleted-repos-dialog/repo-item.js @@ -0,0 +1,71 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import { seafileAPI } from '../../../utils/seafile-api'; +import { gettext, lang } from '../../../utils/constants'; +import toaster from '../../toast'; +import { Utils } from '../../../utils/utils'; + +dayjs.locale(lang); +dayjs.extend(relativeTime); + +const RepoItem = ({ repo, filterRestoredRepo }) => { + const repoID = useMemo(() => repo.repo_id, [repo]); + const repoName = useMemo(() => repo.repo_name, [repo]); + const localTime = useMemo(() => { + const timeDate = dayjs.utc(repo.del_time).toDate(); + return dayjs(timeDate).fromNow(); + }, [repo]); + const iconUrl = useMemo(() => Utils.getLibIconUrl(repo), [repo]); + + const [highlight, setHighlight] = useState(false); + + const onMouseEnter = useCallback(() => { + setHighlight(true); + }, []); + + const onMouseLeave = useCallback(() => { + setHighlight(false); + }, []); + + const restoreDeletedRepo = useCallback((event) => { + event.preventDefault(); + seafileAPI.restoreDeletedRepo(repoID).then(res => { + const message = gettext('Successfully restored the library {library_name}.').replace('{library_name}', repoName); + toaster.success(message); + filterRestoredRepo(repoID); + }).catch(error => { + const errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + }, [repoID, repoName, filterRestoredRepo]); + + return ( + + + {repoName} + {localTime} + + + + + + ); + +}; + +RepoItem.propTypes = { + repo: PropTypes.object.isRequired, + filterRestoredRepo: PropTypes.func.isRequired, +}; + +export default RepoItem; diff --git a/frontend/src/components/dialog/my-deleted-repos-dialog/repos.js b/frontend/src/components/dialog/my-deleted-repos-dialog/repos.js new file mode 100644 index 0000000000..e50daafe49 --- /dev/null +++ b/frontend/src/components/dialog/my-deleted-repos-dialog/repos.js @@ -0,0 +1,54 @@ +import React, { useState, useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; +import RepoItem from './repo-item'; +import { gettext, trashReposExpireDays } from '../../../utils/constants'; + +const Repos = ({ repos, filterRestoredRepo }) => { + const [containerWidth, setContainerWidth] = useState(0); + + const containerRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + const handleResize = () => { + if (!container) return; + setContainerWidth(container.offsetWidth); + }; + const resizeObserver = new ResizeObserver(handleResize); + container && resizeObserver.observe(container); + + return () => { + container && resizeObserver.unobserve(container); + }; + }, []); + + return ( +
+

{gettext('Tip: libraries deleted {placeholder} days ago will be cleaned automatically.').replace('{placeholder}', trashReposExpireDays)}

+ + + + + + + + + + + {repos.map((repo) => { + return ( + + ); + })} + +
{/* img*/}{gettext('Name')}{gettext('Deleted Time')}
+
+ ); +}; + +Repos.propTypes = { + repos: PropTypes.array, + filterRestoredRepo: PropTypes.func, +}; + +export default Repos; diff --git a/frontend/src/components/dialog/my-deleted-repos.js b/frontend/src/components/dialog/my-deleted-repos.js deleted file mode 100644 index c68d8388c8..0000000000 --- a/frontend/src/components/dialog/my-deleted-repos.js +++ /dev/null @@ -1,168 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Modal, ModalHeader, ModalBody } from 'reactstrap'; -import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; -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'; - -dayjs.locale(lang); -dayjs.extend(relativeTime); - -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 ( - - - {gettext('Deleted Libraries')} - - - {this.state.isLoading && } - {(!this.state.isLoading && repos.length === 0) && - - } - {repos.length !== 0 && -
-

{gettext('Tip: libraries deleted {placeholder} days ago will be cleaned automatically.').replace('{placeholder}', trashReposExpireDays)}

- - - - - - - - - - - {repos.map((item) => { - return ( - - ); - })} - -
{/* img*/}{gettext('Name')}{gettext('Deleted Time')}
-
- } -
-
- ); - } -} - -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 = dayjs.utc(this.props.repo.del_time).toDate(); - localTime = dayjs(localTime).fromNow(); - let iconUrl = Utils.getLibIconUrl(this.props.repo); - - return ( - - - {this.props.repo.repo_name} - {localTime} - - - - - - ); - } -} - -DeletedRepoItem.propTypes = { - repo: PropTypes.object.isRequired, - refreshDeletedRepoList: PropTypes.func.isRequired, -}; - -MyLibsDeleted.propTypes = { - toggleDialog: PropTypes.func.isRequired -}; - -export default MyLibsDeleted; diff --git a/frontend/src/pages/my-libs/my-libs.js b/frontend/src/pages/my-libs/my-libs.js index 17d59549c1..4b74b3e67a 100644 --- a/frontend/src/pages/my-libs/my-libs.js +++ b/frontend/src/pages/my-libs/my-libs.js @@ -14,7 +14,7 @@ import ReposSortMenu from '../../components/sort-menu'; import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; import SortOptionsDialog from '../../components/dialog/sort-options'; import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; -import DeletedReposDialog from '../../components/dialog/my-deleted-repos'; +import DeletedReposDialog from '../../components/dialog/my-deleted-repos-dialog'; import { LIST_MODE } from '../../components/dir-view-mode/constants'; import MylibRepoListView from './mylib-repo-list-view';