From 49bd39f7dfb4f04b403e252a2ac647c7cce1d588 Mon Sep 17 00:00:00 2001 From: llj Date: Sat, 21 Dec 2024 18:22:52 +0800 Subject: [PATCH] Repo history redesign (#7229) * [repo history] display it with a dialog instead of an independent page * [repo history] redesigned the 'commit details' dialog * [repo history] added prop type checking --- frontend/src/assets/icons/time.svg | 14 + .../src/components/dialog/commit-details.js | 14 +- .../dialog/edit-repo-commit-labels.js | 2 +- .../src/components/dialog/repo-history.js | 314 ++++++++++++++++++ .../dir-view-mode/dir-others/index.js | 25 +- frontend/src/components/paginator.js | 11 +- frontend/src/css/commit-details.css | 10 +- frontend/src/css/repo-history.css | 21 ++ 8 files changed, 392 insertions(+), 19 deletions(-) create mode 100644 frontend/src/assets/icons/time.svg create mode 100644 frontend/src/components/dialog/repo-history.js diff --git a/frontend/src/assets/icons/time.svg b/frontend/src/assets/icons/time.svg new file mode 100644 index 0000000000..05aa5e6934 --- /dev/null +++ b/frontend/src/assets/icons/time.svg @@ -0,0 +1,14 @@ + + + + +time + + + + diff --git a/frontend/src/components/dialog/commit-details.js b/frontend/src/components/dialog/commit-details.js index 1dade2f761..2b0d1b632c 100644 --- a/frontend/src/components/dialog/commit-details.js +++ b/frontend/src/components/dialog/commit-details.js @@ -6,6 +6,7 @@ import { gettext } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import Loading from '../loading'; +import Icon from '../icon'; import '../../css/commit-details.css'; @@ -45,10 +46,13 @@ class CommitDetails extends React.Component { render() { const { toggleDialog, commitTime } = this.props; return ( - + {gettext('Modification Details')} -

{dayjs(commitTime).format('YYYY-MM-DD HH:mm:ss')}

+

+ + {dayjs(commitTime).format('YYYY-MM-DD HH:mm:ss')} +

@@ -87,11 +91,11 @@ class Content extends React.Component { } return ( -
{item.title}
-
    +
    {item.title}
    +
      { data[item.type].map((item, index) => { - return
    • ; + return
    • ; }) }
    diff --git a/frontend/src/components/dialog/edit-repo-commit-labels.js b/frontend/src/components/dialog/edit-repo-commit-labels.js index 2ca9ebb8a2..f5a488c205 100644 --- a/frontend/src/components/dialog/edit-repo-commit-labels.js +++ b/frontend/src/components/dialog/edit-repo-commit-labels.js @@ -57,7 +57,7 @@ class UpdateRepoCommitLabels extends React.Component { render() { const { formErrorMsg } = this.state; return ( - + {gettext('Edit labels')} diff --git a/frontend/src/components/dialog/repo-history.js b/frontend/src/components/dialog/repo-history.js new file mode 100644 index 0000000000..d835bb5c03 --- /dev/null +++ b/frontend/src/components/dialog/repo-history.js @@ -0,0 +1,314 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import dayjs from 'dayjs'; +import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +import { Utils } from '../../utils/utils'; +import { gettext, siteRoot, enableRepoSnapshotLabel as showLabel } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import Loading from '../../components/loading'; +import Paginator from '../../components/paginator'; +import ModalPortal from '../../components/modal-portal'; +import CommitDetails from '../../components/dialog/commit-details'; +import UpdateRepoCommitLabels from '../../components/dialog/edit-repo-commit-labels'; + +import '../../css/repo-history.css'; + +const propTypes = { + repoID: PropTypes.string.isRequired, + userPerm: PropTypes.string.isRequired, + currentRepoInfo: PropTypes.object.isRequired, + toggleDialog: PropTypes.func.isRequired +}; + +class RepoHistory extends React.Component { + + constructor(props) { + super(props); + this.state = { + isLoading: true, + errorMsg: '', + currentPage: 1, + perPage: 100, + hasNextPage: false, + items: [] + }; + } + + componentDidMount() { + this.getItems(this.state.currentPage); + } + + getItems = (page) => { + const { repoID } = this.props; + seafileAPI.getRepoHistory(repoID, page, this.state.perPage).then((res) => { + this.setState({ + isLoading: false, + currentPage: page, + items: res.data.data, + hasNextPage: res.data.more + }); + }).catch((error) => { + this.setState({ + isLoading: false, + errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403 + }); + }); + }; + + resetPerPage = (perPage) => { + this.setState({ + perPage: perPage + }, () => { + this.getItems(1); + }); + }; + + render() { + const { repoID, userPerm, currentRepoInfo, toggleDialog } = this.props; + const { repo_name: repoName } = currentRepoInfo; + + let title = gettext('{placeholder} Modification History'); + title = title.replace('{placeholder}', '' + Utils.HTMLescape(repoName) + ''); + + return ( + + + + + + {userPerm == 'rw' &&

    {gettext('Tip: a snapshot will be generated after modification, which records the library state after the modification.')}

    } + +
    +
    + ); + } +} + +class Content extends React.Component { + + constructor(props) { + super(props); + this.theadData = showLabel ? [ + { width: '43%', text: gettext('Description') }, + { width: '12%', text: gettext('Time') }, + { width: '9%', text: gettext('Modifier') }, + { width: '12%', text: `${gettext('Device')} / ${gettext('Version')}` }, + { width: '12%', text: gettext('Labels') }, + { width: '12%', text: '' } + ] : [ + { width: '43%', text: gettext('Description') }, + { width: '15%', text: gettext('Time') }, + { width: '15%', text: gettext('Modifier') }, + { width: '15%', text: `${gettext('Device')} / ${gettext('Version')}` }, + { width: '12%', text: '' } + ]; + } + + getPreviousPage = () => { + this.props.getListByPage(this.props.currentPage - 1); + }; + + getNextPage = () => { + this.props.getListByPage(this.props.currentPage + 1); + }; + + render() { + const { + isLoading, errorMsg, items, + curPerPage, currentPage, hasNextPage + } = this.props; + + if (isLoading) { + return ; + } + + if (errorMsg) { + return

    {errorMsg}

    ; + } + + return ( + + + + + {this.theadData.map((item, index) => { + return ; + })} + + + + {items.map((item, index) => { + item.isFirstCommit = (currentPage == 1) && (index == 0); + item.showDetails = hasNextPage || (index != items.length - 1); + return ; + })} + +
    {item.text}
    + +
    + ); + } +} + +Content.propTypes = { + isLoading: PropTypes.bool.isRequired, + errorMsg: PropTypes.string.isRequired, + items: PropTypes.array.isRequired, + currentPage: PropTypes.number.isRequired, + hasNextPage: PropTypes.bool.isRequired, + curPerPage: PropTypes.number.isRequired, + resetPerPage: PropTypes.func.isRequired, + getListByPage: PropTypes.func.isRequired, +}; + +class Item extends React.Component { + + constructor(props) { + super(props); + this.state = { + labels: this.props.item.tags, + isIconShown: false, + isCommitLabelUpdateDialogOpen: false, + isCommitDetailsDialogOpen: false + }; + } + + handleMouseOver = () => { + this.setState({ isIconShown: true }); + }; + + handleMouseOut = () => { + this.setState({ isIconShown: false }); + }; + + showCommitDetails = (e) => { + e.preventDefault(); + this.setState({ + isCommitDetailsDialogOpen: !this.state.isCommitDetailsDialogOpen + }); + }; + + toggleCommitDetailsDialog = () => { + this.setState({ + isCommitDetailsDialogOpen: !this.state.isCommitDetailsDialogOpen + }); + }; + + editLabel = (e) => { + e.preventDefault(); + this.setState({ + isCommitLabelUpdateDialogOpen: !this.state.isCommitLabelUpdateDialogOpen + }); + }; + + toggleLabelEditDialog = () => { + this.setState({ + isCommitLabelUpdateDialogOpen: !this.state.isCommitLabelUpdateDialogOpen + }); + }; + + updateLabels = (labels) => { + this.setState({ + labels: labels + }); + }; + + render() { + const { item, repoID, userPerm } = this.props; + const { isIconShown, isCommitLabelUpdateDialogOpen, isCommitDetailsDialogOpen, labels } = this.state; + + let name = ''; + if (item.email) { + if (!item.second_parent_id) { + name = {item.name}; + } else { + name = gettext('None'); + } + } else { + name = gettext('Unknown'); + } + + return ( + + + + {item.description} + {item.showDetails && + {gettext('Details')} + } + + {dayjs(item.time).format('YYYY-MM-DD')} + {name} + + {item.client_version ? `${item.device_name} / ${item.client_version}` : 'API / --'} + + {showLabel && + + {labels.map((item, index) => { + return {item}; + })} + {userPerm == 'rw' && + + } + + } + + {userPerm == 'rw' && ( + item.isFirstCommit ? + {gettext('Current Version')} : + {gettext('View Snapshot')} + )} + + + {isCommitDetailsDialogOpen && + + + + } + {isCommitLabelUpdateDialogOpen && + + + + } + + ); + } +} + +Item.propTypes = { + item: PropTypes.object.isRequired, +}; + +RepoHistory.propTypes = propTypes; + +export default RepoHistory; diff --git a/frontend/src/components/dir-view-mode/dir-others/index.js b/frontend/src/components/dir-view-mode/dir-others/index.js index 0eb4a59e7c..176554da23 100644 --- a/frontend/src/components/dir-view-mode/dir-others/index.js +++ b/frontend/src/components/dir-view-mode/dir-others/index.js @@ -1,10 +1,11 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { gettext, siteRoot } from '../../../utils/constants'; +import { gettext } from '../../../utils/constants'; import { Utils } from '../../../utils/utils'; import TreeSection from '../../tree-section'; import TrashDialog from '../../dialog/trash-dialog'; import LibSettingsDialog from '../../dialog/lib-settings'; +import RepoHistoryDialog from '../../dialog/repo-history'; import './index.css'; @@ -16,15 +17,15 @@ const DirOthers = ({ userPerm, repoID, currentRepoInfo }) => { }; const [showTrashDialog, setShowTrashDialog] = useState(false); - let trashUrl = null; - const historyUrl = siteRoot + 'repo/history/' + repoID + '/'; - if (userPerm === 'rw') { - trashUrl = siteRoot + 'repo/' + repoID + '/trash/'; - } const toggleTrashDialog = () => { setShowTrashDialog(!showTrashDialog); }; + let [isRepoHistoryDialogOpen, setRepoHistoryDialogOpen] = useState(false); + const toggleRepoHistoryDialog = () => { + setRepoHistoryDialogOpen(!isRepoHistoryDialogOpen); + }; + return ( {showSettings && ( @@ -33,14 +34,14 @@ const DirOthers = ({ userPerm, repoID, currentRepoInfo }) => { {gettext('Settings')} )} - {trashUrl && ( + {userPerm == 'rw' && (
    {gettext('Trash')}
    )} {Utils.isDesktop() && ( -
    location.href = historyUrl}> +
    {gettext('History')}
    @@ -60,6 +61,14 @@ const DirOthers = ({ userPerm, repoID, currentRepoInfo }) => { toggleDialog={toggleSettingsDialog} /> )} + {isRepoHistoryDialogOpen && ( + + )} ); }; diff --git a/frontend/src/components/paginator.js b/frontend/src/components/paginator.js index 4d74ca651e..42800e7dc6 100644 --- a/frontend/src/components/paginator.js +++ b/frontend/src/components/paginator.js @@ -13,10 +13,11 @@ const propTypes = { gotoNextPage: PropTypes.func.isRequired, hasNextPage: PropTypes.bool.isRequired, resetPerPage: PropTypes.func.isRequired, - curPerPage: PropTypes.number.isRequired + curPerPage: PropTypes.number.isRequired, + noURLUpdate: PropTypes.bool }; -const PAGES = [25, 50, 100]; +const PER_PAGES = [25, 50, 100]; class Paginator extends Component { @@ -45,6 +46,10 @@ class Paginator extends Component { }; updateURL = (page, perPage) => { + const { noURLUpdate = false } = this.props; + if (noURLUpdate) { + return; + } let url = new URL(location.href); let searchParams = new URLSearchParams(url.search); searchParams.set('page', page); @@ -106,7 +111,7 @@ class Paginator extends Component { - {PAGES.map(perPage => { + {PER_PAGES.map(perPage => { return this.renderDropdownItem(curPerPage, perPage); })} diff --git a/frontend/src/css/commit-details.css b/frontend/src/css/commit-details.css index f0255953ed..3dac6eb22a 100644 --- a/frontend/src/css/commit-details.css +++ b/frontend/src/css/commit-details.css @@ -1,3 +1,9 @@ -.commit-detail-item { - list-style-type: none; +.repo-commit-time { + font-size: .875rem; +} + +.repo-commit-time .seafile-multicolor-icon-time { + width: 1rem; + height: 1rem; + color: #999; } diff --git a/frontend/src/css/repo-history.css b/frontend/src/css/repo-history.css index b30cc6cd92..6f19c8f23f 100644 --- a/frontend/src/css/repo-history.css +++ b/frontend/src/css/repo-history.css @@ -39,3 +39,24 @@ body { .go-back .sf3-font-down { font-size: 1.75rem !important; } + +/* for the dialog */ +.repo-snapshot-tip { + font-size: .875rem; + color: #666; +} + +#repo-history-dialog.modal-dialog { + max-height: calc(100% - 3.5rem); + overflow: hidden; + height: calc(100% - 3.5rem); +} + +#repo-history-dialog .modal-content { + max-height: 100%; + overflow: hidden; +} + +#repo-history-dialog .modal-body { + overflow: auto; +}