review-mode-switcher.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import { forwardRef, memo, MouseEventHandler } from 'react'
  2. import {
  3. Dropdown,
  4. DropdownMenu,
  5. DropdownToggle,
  6. } from '@/shared/components/dropdown/dropdown-menu'
  7. import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
  8. import MaterialIcon from '@/shared/components/material-icon'
  9. import classNames from 'classnames'
  10. import { useTrackChangesStateActionsContext } from '../context/track-changes-state-context'
  11. import { useUserContext } from '@/shared/context/user-context'
  12. import { useTranslation } from 'react-i18next'
  13. import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
  14. import usePersistedState from '@/shared/hooks/use-persisted-state'
  15. import { sendMB } from '@/infrastructure/event-tracking'
  16. import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
  17. import { useProjectContext } from '@/shared/context/project-context'
  18. import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context'
  19. import { useEditorContext } from '@/shared/context/editor-context'
  20. import { useTrackingChangesMode } from '@/shared/hooks/use-tracking-changes-mode'
  21. function ReviewModeSwitcher() {
  22. const { t } = useTranslation()
  23. const user = useUserContext()
  24. const { saveTrackChangesForCurrentUser, saveTrackChanges } =
  25. useTrackChangesStateActionsContext()
  26. const mode = useTrackingChangesMode()
  27. const { permissionsLevel } = useIdeReactContext()
  28. const { write, trackedWrite } = usePermissionsContext()
  29. const { features } = useProjectContext()
  30. const { setUpgradeTrackChangesModal } = useEditorContext()
  31. const showViewOption = permissionsLevel === 'readOnly'
  32. const view = useCodeMirrorViewContext()
  33. return (
  34. <div className="review-mode-switcher-container">
  35. <Dropdown className="review-mode-switcher" align="end">
  36. <DropdownToggle
  37. as={ModeSwitcherToggleButton}
  38. id="review-mode-switcher"
  39. />
  40. <DropdownMenu flip={false}>
  41. <OLDropdownMenuItem
  42. disabled={!write}
  43. onClick={() => {
  44. if (mode === 'edit') {
  45. view.focus()
  46. return
  47. }
  48. sendMB('editing-mode-change', {
  49. role: permissionsLevel,
  50. previousMode: mode,
  51. newMode: 'edit',
  52. })
  53. if (user?.id) {
  54. saveTrackChangesForCurrentUser(false)
  55. } else {
  56. saveTrackChanges({ on_for_guests: false })
  57. }
  58. view.focus()
  59. }}
  60. description={t('edit_content_directly')}
  61. leadingIcon="edit"
  62. active={write && mode === 'edit'}
  63. >
  64. {t('editing')}
  65. </OLDropdownMenuItem>
  66. <OLDropdownMenuItem
  67. disabled={permissionsLevel === 'readOnly'}
  68. onClick={() => {
  69. if (mode === 'review') {
  70. view.focus()
  71. return
  72. }
  73. if (!features.trackChanges) {
  74. setUpgradeTrackChangesModal({
  75. show: true,
  76. location: 'review-switcher',
  77. })
  78. } else {
  79. sendMB('editing-mode-change', {
  80. role: permissionsLevel,
  81. previousMode: mode,
  82. newMode: 'review',
  83. })
  84. if (user?.id) {
  85. saveTrackChangesForCurrentUser(true)
  86. } else {
  87. saveTrackChanges({ on_for_guests: true })
  88. }
  89. view.focus()
  90. }
  91. }}
  92. description={
  93. permissionsLevel === 'review' && !trackedWrite
  94. ? t('comment_only')
  95. : t('edits_become_suggestions')
  96. }
  97. leadingIcon="rate_review"
  98. active={trackedWrite && mode === 'review'}
  99. >
  100. {t('reviewing')}
  101. </OLDropdownMenuItem>
  102. {showViewOption && (
  103. <OLDropdownMenuItem
  104. description={t('can_view_content')}
  105. leadingIcon="visibility"
  106. active={mode === 'view'}
  107. >
  108. {t('viewing')}
  109. </OLDropdownMenuItem>
  110. )}
  111. </DropdownMenu>
  112. </Dropdown>
  113. </div>
  114. )
  115. }
  116. const ModeSwitcherToggleButton = forwardRef<
  117. HTMLButtonElement,
  118. { onClick: MouseEventHandler<HTMLButtonElement>; 'aria-expanded': boolean }
  119. >(({ onClick, 'aria-expanded': ariaExpanded }, ref) => {
  120. const { t } = useTranslation()
  121. const mode = useTrackingChangesMode()
  122. if (mode === 'edit') {
  123. return (
  124. <ModeSwitcherToggleButtonContent
  125. ref={ref}
  126. onClick={onClick}
  127. className="editing"
  128. iconType="edit"
  129. label={t('editing')}
  130. ariaExpanded={ariaExpanded}
  131. />
  132. )
  133. } else if (mode === 'review') {
  134. return (
  135. <ModeSwitcherToggleButtonContent
  136. ref={ref}
  137. onClick={onClick}
  138. className="reviewing"
  139. iconType="rate_review"
  140. label={t('reviewing')}
  141. ariaExpanded={ariaExpanded}
  142. />
  143. )
  144. }
  145. return (
  146. <ModeSwitcherToggleButtonContent
  147. ref={ref}
  148. onClick={onClick}
  149. className="viewing"
  150. iconType="visibility"
  151. label={t('viewing')}
  152. ariaExpanded={ariaExpanded}
  153. />
  154. )
  155. })
  156. const ModeSwitcherToggleButtonContent = forwardRef<
  157. HTMLButtonElement,
  158. {
  159. onClick: MouseEventHandler<HTMLButtonElement>
  160. className: string
  161. iconType: string
  162. label: string
  163. ariaExpanded: boolean
  164. }
  165. >(({ onClick, className, iconType, label, ariaExpanded }, ref) => {
  166. const [isFirstTimeUsed, setIsFirstTimeUsed] = usePersistedState(
  167. `modeSwitcherFirstTimeUsed`,
  168. true
  169. )
  170. return (
  171. <button
  172. className={classNames('review-mode-switcher-toggle-button', className, {
  173. 'review-mode-switcher-toggle-button-expanded': isFirstTimeUsed,
  174. })}
  175. ref={ref}
  176. onClick={event => {
  177. setIsFirstTimeUsed(false)
  178. onClick(event)
  179. }}
  180. aria-expanded={ariaExpanded}
  181. aria-label={label}
  182. >
  183. <MaterialIcon type={iconType} />
  184. <div className="review-mode-switcher-toggle-label">{label}</div>
  185. <MaterialIcon type="keyboard_arrow_down" />
  186. </button>
  187. )
  188. })
  189. ModeSwitcherToggleButton.displayName = 'ModeSwitcherToggleButton'
  190. ModeSwitcherToggleButtonContent.displayName = 'ModeSwitcherToggleButtonContent'
  191. export default memo(ReviewModeSwitcher)