Merge pull request #13710 from overleaf/ii-review-panel-migration-bulk-actions-entry

[web] Create bulk actions entry and bulk actions modal

GitOrigin-RevId: c88ce6213304a110ee7410529813310b863178c1
This commit is contained in:
ilkin-overleaf 2023-07-10 15:12:22 +03:00 committed by Copybot
parent 9e8e124113
commit 13a7e752d5
11 changed files with 223 additions and 14 deletions

View file

@ -14,6 +14,7 @@
"about_to_leave_projects": "",
"about_to_trash_projects": "",
"accept": "",
"accept_all": "",
"accept_invitation": "",
"accepted_invite": "",
"access_denied": "",
@ -87,6 +88,8 @@
"blank_project": "",
"blocked_filename": "",
"browser": "",
"bulk_accept_confirm": "",
"bulk_reject_confirm": "",
"by_subscribing_you_agree_to_our_terms_of_service": "",
"can_edit": "",
"can_link_institution_email_acct_to_institution_acct": "",
@ -809,6 +812,7 @@
"refreshing": "",
"regards": "",
"reject": "",
"reject_all": "",
"relink_your_account": "",
"remote_service_error": "",
"remove": "",

View file

@ -7,7 +7,7 @@ import ChangeEntry from './entries/change-entry'
import AggregateChangeEntry from './entries/aggregate-change-entry'
import CommentEntry from './entries/comment-entry'
import AddCommentEntry from './entries/add-comment-entry'
import BulkActionsEntry from './entries/bulk-actions-entry'
import BulkActionsEntry from './entries/bulk-actions-entry/bulk-actions-entry'
import {
useReviewPanelUpdaterFnsContext,
useReviewPanelValueContext,
@ -24,6 +24,7 @@ function CurrentFileContainer() {
permissions,
loadingThreads,
users,
nVisibleSelectedChanges: nChanges,
toggleReviewPanel,
} = useReviewPanelValueContext()
const { setEntryHover } = useReviewPanelUpdaterFnsContext()
@ -114,7 +115,13 @@ function CurrentFileContainer() {
}
if (entry.type === 'bulk-actions') {
return <BulkActionsEntry key={id} />
return (
<BulkActionsEntry
key={id}
entry={entry}
nChanges={nChanges}
/>
)
}
return null

View file

@ -1,7 +0,0 @@
import EntryContainer from './entry-container'
function BulkActionsEntry() {
return <EntryContainer>Bulk actions entry</EntryContainer>
}
export default BulkActionsEntry

View file

@ -0,0 +1,66 @@
import { useTranslation } from 'react-i18next'
import EntryContainer from '../entry-container'
import EntryCallout from '../entry-callout'
import Icon from '../../../../../../shared/components/icon'
import BulkActions from './bulk-actions'
import Modal, { useBulkActionsModal } from './modal'
import { ReviewPanelBulkActionsEntry } from '../../../../../../../../types/review-panel/entry'
type BulkActionsEntryProps = {
entry: ReviewPanelBulkActionsEntry
nChanges: number
}
function BulkActionsEntry({ entry, nChanges }: BulkActionsEntryProps) {
const { t } = useTranslation()
const {
show,
setShow,
isAccept,
handleShowBulkAcceptDialog,
handleShowBulkRejectDialog,
handleConfirmDialog,
} = useBulkActionsModal()
return (
<>
<EntryContainer>
{nChanges > 1 && (
<>
<EntryCallout
className="rp-entry-callout-bulk-actions"
style={{
top: entry.screenPos
? entry.screenPos.y + entry.screenPos.height - 1 + 'px'
: undefined,
}}
/>
<BulkActions
className="rp-entry"
style={{
top: entry.screenPos.y + 'px',
visibility: entry.visible ? 'visible' : 'hidden',
}}
>
<BulkActions.Button onClick={handleShowBulkRejectDialog}>
<Icon type="times" /> {t('reject_all')} ({nChanges})
</BulkActions.Button>
<BulkActions.Button onClick={handleShowBulkAcceptDialog}>
<Icon type="check" /> {t('accept_all')} ({nChanges})
</BulkActions.Button>
</BulkActions>
</>
)}
</EntryContainer>
<Modal
show={show}
setShow={setShow}
isAccept={isAccept}
nChanges={nChanges}
onConfirm={handleConfirmDialog}
/>
</>
)
}
export default BulkActionsEntry

View file

@ -0,0 +1,24 @@
import classnames from 'classnames'
function BulkActions({
className,
...rest
}: React.ComponentPropsWithoutRef<'div'>) {
return (
<div className={classnames('rp-entry-bulk-actions', className)} {...rest} />
)
}
BulkActions.Button = function BulkActionsButton({
className,
...rest
}: React.ComponentPropsWithoutRef<'button'>) {
return (
<button
className={classnames('rp-bulk-actions-btn', className)}
{...rest}
/>
)
}
export default BulkActions

View file

@ -0,0 +1,87 @@
import { useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Button, Modal as BootstrapModal } from 'react-bootstrap'
import AccessibleModal from '../../../../../../shared/components/accessible-modal'
import { useReviewPanelValueContext } from '../../../../context/review-panel/review-panel-context'
type BulkActionsModalProps = {
show: boolean
setShow: React.Dispatch<React.SetStateAction<boolean>>
isAccept: boolean
nChanges: number
onConfirm: () => void
}
function Modal({
show,
setShow,
isAccept,
nChanges,
onConfirm,
}: BulkActionsModalProps) {
const { t } = useTranslation()
return (
<AccessibleModal show={show} onHide={() => setShow(false)}>
<BootstrapModal.Header closeButton>
<h3>{isAccept ? t('accept_all') : t('reject_all')}</h3>
</BootstrapModal.Header>
<BootstrapModal.Body>
<p>
{isAccept
? t('bulk_accept_confirm', { nChanges })
: t('bulk_reject_confirm', { nChanges })}
</p>
</BootstrapModal.Body>
<BootstrapModal.Footer>
<Button
bsStyle={null}
className="btn-secondary"
onClick={() => setShow(false)}
>
{t('cancel')}
</Button>
<Button bsStyle={null} className="btn-primary" onClick={onConfirm}>
{t('ok')}
</Button>
</BootstrapModal.Footer>
</AccessibleModal>
)
}
export function useBulkActionsModal() {
const [show, setShow] = useState(false)
const [isAccept, setIsAccept] = useState(false)
const { bulkAcceptActions, bulkRejectActions } = useReviewPanelValueContext()
const handleShowBulkAcceptDialog = useCallback(() => {
setIsAccept(true)
setShow(true)
}, [])
const handleShowBulkRejectDialog = useCallback(() => {
setIsAccept(false)
setShow(true)
}, [])
const handleConfirmDialog = useCallback(() => {
if (isAccept) {
bulkAcceptActions()
} else {
bulkRejectActions()
}
setShow(false)
}, [bulkAcceptActions, bulkRejectActions, isAccept])
return {
show,
setShow,
isAccept,
handleShowBulkAcceptDialog,
handleShowBulkRejectDialog,
handleConfirmDialog,
}
}
export default Modal

View file

@ -16,6 +16,9 @@ function useAngularReviewPanelState(): ReviewPanelState {
const [loading] = useScopeValue<ReviewPanel.Value<'loading'>>(
'reviewPanel.overview.loading'
)
const [nVisibleSelectedChanges] = useScopeValue<
ReviewPanel.Value<'nVisibleSelectedChanges'>
>('reviewPanel.nVisibleSelectedChanges')
const [collapsed, setCollapsed] = useScopeValue<
ReviewPanel.Value<'collapsed'>
>('reviewPanel.overview.docsCollapsedState')
@ -97,6 +100,10 @@ function useAngularReviewPanelState(): ReviewPanelState {
useScopeValue<ReviewPanel.Value<'acceptChanges'>>('acceptChanges')
const [rejectChanges] =
useScopeValue<ReviewPanel.Value<'rejectChanges'>>('rejectChanges')
const [bulkAcceptActions] =
useScopeValue<ReviewPanel.Value<'bulkAcceptActions'>>('bulkAcceptActions')
const [bulkRejectActions] =
useScopeValue<ReviewPanel.Value<'bulkRejectActions'>>('bulkRejectActions')
const handleSetSubview = useCallback(
(subView: SubView) => {
@ -132,6 +139,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
gotoEntry,
handleLayoutChange,
loadingThreads,
nVisibleSelectedChanges,
permissions,
users,
resolveComment,
@ -152,6 +160,8 @@ function useAngularReviewPanelState(): ReviewPanelState {
trackChangesForGuestsAvailable,
formattedProjectMembers,
toggleReviewPanel,
bulkAcceptActions,
bulkRejectActions,
unresolveComment,
deleteThread,
refreshResolvedCommentsDropdown,
@ -169,6 +179,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
gotoEntry,
handleLayoutChange,
loadingThreads,
nVisibleSelectedChanges,
permissions,
users,
resolveComment,
@ -189,6 +200,8 @@ function useAngularReviewPanelState(): ReviewPanelState {
trackChangesForGuestsAvailable,
formattedProjectMembers,
toggleReviewPanel,
bulkAcceptActions,
bulkRejectActions,
unresolveComment,
deleteThread,
refreshResolvedCommentsDropdown,

View file

@ -24,6 +24,7 @@ export interface ReviewPanelState {
gotoEntry: (docId: DocId, entryOffset: number) => void
handleLayoutChange: () => void
loadingThreads: boolean
nVisibleSelectedChanges: number
permissions: ReviewPanelPermissions
users: ReviewPanelUsers
resolveComment: (docId: DocId, entryId: ThreadId) => void
@ -54,6 +55,8 @@ export interface ReviewPanelState {
}
>
toggleReviewPanel: () => void
bulkAcceptActions: () => void
bulkRejectActions: () => void
unresolveComment: (threadId: ThreadId) => void
deleteThread: (_entryId: unknown, docId: DocId, threadId: ThreadId) => void
refreshResolvedCommentsDropdown: () => Promise<void>

View file

@ -688,7 +688,7 @@ export default App.controller(
dispatchReviewPanelEvent('changes:reject', change_ids)
}
const bulkAccept = function () {
ide.$scope.bulkAcceptActions = function () {
_doAcceptChanges(ide.$scope.reviewPanel.selectedEntryIds.slice())
eventTracking.sendMB('rp-bulk-accept', {
view: $scope.ui.reviewPanelOpen
@ -698,7 +698,7 @@ export default App.controller(
})
}
const bulkReject = function () {
ide.$scope.bulkRejectActions = function () {
_doRejectChanges(ide.$scope.reviewPanel.selectedEntryIds.slice())
eventTracking.sendMB('rp-bulk-reject', {
view: $scope.ui.reviewPanelOpen
@ -729,9 +729,9 @@ export default App.controller(
})
.result.then(function (isAccept) {
if (isAccept) {
return bulkAccept()
return ide.$scope.bulkAcceptActions()
} else {
return bulkReject()
return ide.$scope.bulkRejectActions()
}
})

View file

@ -232,6 +232,17 @@ describe('<ReviewPanel />', function () {
it.skip('adds comment', function () {})
})
describe('bulk actions entry', function () {
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('renders the reject and accept all buttons`', function () {})
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('accepts all changes', function () {})
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('rejects all changes', function () {})
})
describe('overview mode', function () {
// eslint-disable-next-line mocha/no-skipped-tests
it.skip('shows list of files changed', function () {})

View file

@ -57,8 +57,9 @@ export interface ReviewPanelAddCommentEntry extends ReviewPanelBaseEntry {
type: 'add-comment'
}
interface ReviewPanelBulkActionsEntry extends ReviewPanelBaseEntry {
export interface ReviewPanelBulkActionsEntry extends ReviewPanelBaseEntry {
type: 'bulk-actions'
length: number
}
export type ReviewPanelEntry =