Parcourir la source

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
ilkin-overleaf il y a 3 ans
Parent
commit
13a7e752d5

+ 4 - 0
services/web/frontend/extracted-translations.json

@@ -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": "",

+ 9 - 2
services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx

@@ -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

+ 0 - 7
services/web/frontend/js/features/source-editor/components/review-panel/entries/bulk-actions-entry.tsx

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

+ 66 - 0
services/web/frontend/js/features/source-editor/components/review-panel/entries/bulk-actions-entry/bulk-actions-entry.tsx

@@ -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

+ 24 - 0
services/web/frontend/js/features/source-editor/components/review-panel/entries/bulk-actions-entry/bulk-actions.tsx

@@ -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

+ 87 - 0
services/web/frontend/js/features/source-editor/components/review-panel/entries/bulk-actions-entry/modal.tsx

@@ -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

+ 13 - 0
services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts

@@ -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,

+ 3 - 0
services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts

@@ -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>

+ 4 - 4
services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js

@@ -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()
           }
         })
 

+ 11 - 0
services/web/test/frontend/features/review-panel/review-panel.spec.tsx

@@ -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 () {})

+ 2 - 1
services/web/types/review-panel/entry.ts

@@ -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 =