Add confirm modal on accept/reject selected changes (#21540)

* Add showGenericConfirmModal in ModalsContext

* Add confirm modal on accept/reject selected changes

* plural in translations

* change tooltip to include selected changes

* add _plural to all translated languages

* lowercase title/tooltip

* count replacements as single change

* use new translation key

GitOrigin-RevId: afadbe1eeb2a290688b96f2b5388485f40c958d0
This commit is contained in:
Domagoj Kriskovic 2024-11-11 17:24:51 +01:00 committed by Copybot
parent edb4e3d537
commit 8a90ffa3fb
6 changed files with 195 additions and 7 deletions

View file

@ -37,6 +37,7 @@
"accept_change_error_title": "",
"accept_invitation": "",
"accept_or_reject_each_changes_individually": "",
"accept_selected_changes": "",
"accept_terms_and_conditions": "",
"accepted_invite": "",
"accepting_invite_as": "",
@ -258,11 +259,15 @@
"compromised_password": "",
"configure_sso": "",
"confirm": "",
"confirm_accept_selected_changes": "",
"confirm_accept_selected_changes_plural": "",
"confirm_affiliation": "",
"confirm_affiliation_to_relink_dropbox": "",
"confirm_delete_user_type_email_address": "",
"confirm_new_password": "",
"confirm_primary_email_change": "",
"confirm_reject_selected_changes": "",
"confirm_reject_selected_changes_plural": "",
"confirm_remove_sso_config_enter_email": "",
"confirm_your_email": "",
"confirming": "",
@ -1203,6 +1208,7 @@
"reject": "",
"reject_all": "",
"reject_change": "",
"reject_selected_changes": "",
"relink_your_account": "",
"reload_editor": "",
"remind_before_trial_ends": "",

View file

@ -0,0 +1,53 @@
import { useTranslation } from 'react-i18next'
import { memo } from 'react'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
import OLButton from '@/features/ui/components/ol/ol-button'
import { ButtonProps } from '@/features/ui/components/types/button-props'
export type GenericConfirmModalOwnProps = {
title: string
message: string
onConfirm: () => void
confirmLabel?: string
primaryVariant?: ButtonProps['variant']
}
type GenericConfirmModalProps = React.ComponentProps<typeof OLModal> &
GenericConfirmModalOwnProps
function GenericConfirmModal({
title,
message,
confirmLabel,
primaryVariant = 'primary',
...modalProps
}: GenericConfirmModalProps) {
const { t } = useTranslation()
const handleConfirmClick = modalProps.onConfirm
return (
<OLModal {...modalProps}>
<OLModalHeader closeButton>
<OLModalTitle>{title}</OLModalTitle>
</OLModalHeader>
<OLModalBody className="modal-generic-confirm">{message}</OLModalBody>
<OLModalFooter>
<OLButton variant="secondary" onClick={() => modalProps.onHide()}>
{t('cancel')}
</OLButton>
<OLButton variant={primaryVariant} onClick={handleConfirmClick}>
{confirmLabel || t('ok')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}
export default memo(GenericConfirmModal)

View file

@ -12,9 +12,13 @@ import GenericMessageModal, {
import OutOfSyncModal, {
OutOfSyncModalProps,
} from '@/features/ide-react/components/modals/out-of-sync-modal'
import GenericConfirmModal, {
GenericConfirmModalOwnProps,
} from '../components/modals/generic-confirm-modal'
type ModalsContextValue = {
genericModalVisible: boolean
showGenericConfirmModal: (data: GenericConfirmModalOwnProps) => void
showGenericMessageModal: (
title: GenericMessageModalOwnProps['title'],
message: GenericMessageModalOwnProps['message']
@ -28,8 +32,15 @@ const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
export const ModalsContextProvider: FC = ({ children }) => {
const [showGenericModal, setShowGenericModal] = useState(false)
const [showConfirmModal, setShowConfirmModal] = useState(false)
const [genericMessageModalData, setGenericMessageModalData] =
useState<GenericMessageModalOwnProps>({ title: '', message: '' })
const [genericConfirmModalData, setGenericConfirmModalData] =
useState<GenericConfirmModalOwnProps>({
title: '',
message: '',
onConfirm: () => {},
})
const [shouldShowOutOfSyncModal, setShouldShowOutOfSyncModal] =
useState(false)
@ -41,6 +52,15 @@ export const ModalsContextProvider: FC = ({ children }) => {
setShowGenericModal(false)
}, [])
const handleHideGenericConfirmModal = useCallback(() => {
setShowConfirmModal(false)
}, [])
const handleConfirmGenericConfirmModal = useCallback(() => {
genericConfirmModalData.onConfirm()
setShowConfirmModal(false)
}, [genericConfirmModalData])
const showGenericMessageModal = useCallback(
(
title: GenericMessageModalOwnProps['title'],
@ -52,6 +72,14 @@ export const ModalsContextProvider: FC = ({ children }) => {
[]
)
const showGenericConfirmModal = useCallback(
(data: GenericConfirmModalOwnProps) => {
setGenericConfirmModalData(data)
setShowConfirmModal(true)
},
[]
)
const handleHideOutOfSyncModal = useCallback(() => {
setShouldShowOutOfSyncModal(false)
}, [])
@ -64,10 +92,16 @@ export const ModalsContextProvider: FC = ({ children }) => {
const value = useMemo<ModalsContextValue>(
() => ({
showGenericMessageModal,
showGenericConfirmModal,
genericModalVisible: showGenericModal,
showOutOfSyncModal,
}),
[showGenericMessageModal, showGenericModal, showOutOfSyncModal]
[
showGenericMessageModal,
showGenericConfirmModal,
showGenericModal,
showOutOfSyncModal,
]
)
return (
@ -78,6 +112,12 @@ export const ModalsContextProvider: FC = ({ children }) => {
onHide={handleHideGenericModal}
{...genericMessageModalData}
/>
<GenericConfirmModal
show={showConfirmModal}
onHide={handleHideGenericConfirmModal}
{...genericConfirmModalData}
onConfirm={handleConfirmGenericConfirmModal}
/>
<OutOfSyncModal
{...outOfSyncModalData}
show={shouldShowOutOfSyncModal}

View file

@ -30,6 +30,8 @@ import {
import { isInsertOperation } from '@/utils/operations'
import { isCursorNearViewportEdge } from '@/features/source-editor/utils/is-cursor-near-edge'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { useModalsContext } from '@/features/ide-react/context/modals-context'
import { numberOfChangesInSelection } from '../utils/changes-in-selection'
const ReviewTooltipMenu: FC = () => {
const state = useCodeMirrorStateContext()
@ -72,6 +74,7 @@ const ReviewTooltipMenuContent: FC<{
const { setView } = useReviewPanelViewActionsContext()
const ranges = useRangesContext()
const { acceptChanges, rejectChanges } = useRangesActionsContext()
const { showGenericConfirmModal } = useModalsContext()
const addComment = useCallback(() => {
setReviewPanelOpen(true)
@ -109,12 +112,42 @@ const ReviewTooltipMenuContent: FC<{
}, [ranges, state.selection.main])
const acceptChangesHandler = useCallback(() => {
acceptChanges(...changeIdsInSelection)
}, [acceptChanges, changeIdsInSelection])
const nChanges = numberOfChangesInSelection(ranges, state.selection.main)
showGenericConfirmModal({
message: t('confirm_accept_selected_changes', { count: nChanges }),
title: t('accept_selected_changes'),
onConfirm: () => {
acceptChanges(...changeIdsInSelection)
},
primaryVariant: 'danger',
})
}, [
acceptChanges,
changeIdsInSelection,
ranges,
showGenericConfirmModal,
state.selection.main,
t,
])
const rejectChangesHandler = useCallback(() => {
rejectChanges(...changeIdsInSelection)
}, [rejectChanges, changeIdsInSelection])
const nChanges = numberOfChangesInSelection(ranges, state.selection.main)
showGenericConfirmModal({
message: t('confirm_reject_selected_changes', { count: nChanges }),
title: t('reject_selected_changes'),
onConfirm: () => {
rejectChanges(...changeIdsInSelection)
},
primaryVariant: 'danger',
})
}, [
showGenericConfirmModal,
t,
ranges,
state.selection.main,
rejectChanges,
changeIdsInSelection,
])
const showChangesButtons = changeIdsInSelection.length > 0
@ -130,7 +163,10 @@ const ReviewTooltipMenuContent: FC<{
{showChangesButtons && (
<>
<div className="review-tooltip-menu-divider" />
<OLTooltip id="accept-all-changes" description={t('accept_all')}>
<OLTooltip
id="accept-all-changes"
description={t('accept_selected_changes')}
>
<button
className="review-tooltip-menu-button"
onClick={acceptChangesHandler}
@ -139,7 +175,10 @@ const ReviewTooltipMenuContent: FC<{
</button>
</OLTooltip>
<OLTooltip id="reject-all-changes" description={t('reject_all')}>
<OLTooltip
id="reject-all-changes"
description={t('reject_selected_changes')}
>
<button
className="review-tooltip-menu-button"
onClick={rejectChangesHandler}

View file

@ -0,0 +1,44 @@
import { SelectionRange } from '@codemirror/state'
import { Ranges } from '@/features/review-panel-new/context/ranges-context'
import { isDeleteChange, isInsertChange } from '@/utils/operations'
import { canAggregate } from './can-aggregate'
import { Change, EditOperation } from '../../../../../types/change'
export function numberOfChangesInSelection(
ranges: Ranges | undefined,
selection: SelectionRange
) {
if (!ranges) {
return 0
}
let count = 0
let precedingChange: Change<EditOperation> | null = null
for (const change of ranges.changes) {
if (
precedingChange &&
isInsertChange(precedingChange) &&
isDeleteChange(change) &&
canAggregate(change, precedingChange)
) {
// only count once for the aggregated change
continue
} else if (
isInsertChange(change) &&
change.op.p >= selection.from &&
change.op.p + change.op.i.length <= selection.to
) {
count++
} else if (
isDeleteChange(change) &&
selection.from <= change.op.p &&
change.op.p <= selection.to
) {
count++
}
precedingChange = change
}
return count
}

View file

@ -41,6 +41,7 @@
"accept_change_error_title": "Accept Change Error",
"accept_invitation": "Accept invitation",
"accept_or_reject_each_changes_individually": "Accept or reject each change individually",
"accept_selected_changes": "Accept selected changes",
"accept_terms_and_conditions": "Accept terms and conditions",
"accepted_invite": "Accepted invite",
"accepting_invite_as": "You are accepting this invite as",
@ -348,12 +349,16 @@
"configure_sso": "Configure SSO",
"configured": "Configured",
"confirm": "Confirm",
"confirm_accept_selected_changes": "Are you sure you want to accept the selected change?",
"confirm_accept_selected_changes_plural": "Are you sure you want to accept the selected __count__ changes?",
"confirm_affiliation": "Confirm Affiliation",
"confirm_affiliation_to_relink_dropbox": "Please confirm you are still at the institution and on their license, or upgrade your account in order to relink your Dropbox account.",
"confirm_delete_user_type_email_address": "To confirm you want to delete __userName__ please type the email address associated with their account",
"confirm_email": "Confirm Email",
"confirm_new_password": "Confirm New Password",
"confirm_primary_email_change": "Confirm primary email change",
"confirm_reject_selected_changes": "Are you sure you want to reject the selected change?",
"confirm_reject_selected_changes_plural": "Are you sure you want to reject the selected __count__ changes?",
"confirm_remove_sso_config_enter_email": "To confirm you want to remove your SSO configuration, enter your email address:",
"confirm_your_email": "Confirm your email address",
"confirmation_link_broken": "Sorry, something is wrong with your confirmation link. Please try copy and pasting the link from the bottom of your confirmation email.",
@ -1712,6 +1717,7 @@
"reject": "Reject",
"reject_all": "Reject all",
"reject_change": "Reject change",
"reject_selected_changes": "Reject selected changes",
"related_tags": "Related Tags",
"relink_your_account": "Re-link your account",
"reload_editor": "Reload editor",