review-panel-comment.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { memo, useCallback, useMemo, useState } from 'react'
  2. import { Change, CommentOperation } from '../../../../../types/change'
  3. import {
  4. useThreadsActionsContext,
  5. useThreadsContext,
  6. } from '../context/threads-context'
  7. import classnames from 'classnames'
  8. import { ReviewPanelEntry } from './review-panel-entry'
  9. import { ReviewPanelCommentContent } from './review-panel-comment-content'
  10. import {
  11. CommentId,
  12. ThreadId,
  13. } from '../../../../../types/review-panel/review-panel'
  14. import { useModalsContext } from '@/features/ide-react/context/modals-context'
  15. import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
  16. import { useTranslation } from 'react-i18next'
  17. import { debugConsole } from '@/utils/debugging'
  18. export const ReviewPanelComment = memo<{
  19. comment: Change<CommentOperation>
  20. docId: string
  21. top?: number
  22. hoverRanges?: boolean
  23. handleEnter?: (changeId: string) => void
  24. handleLeave?: (changeId: string) => void
  25. hovered?: boolean
  26. }>(
  27. ({ comment, top, hovered, handleEnter, handleLeave, docId, hoverRanges }) => {
  28. const threads = useThreadsContext()
  29. const {
  30. resolveThread,
  31. editMessage,
  32. deleteMessage,
  33. deleteOwnMessage,
  34. deleteThread,
  35. addMessage,
  36. } = useThreadsActionsContext()
  37. const { showGenericMessageModal } = useModalsContext()
  38. const { t } = useTranslation()
  39. const permissions = usePermissionsContext()
  40. const [processing, setProcessing] = useState(false)
  41. const handleResolveComment = useCallback(async () => {
  42. setProcessing(true)
  43. try {
  44. await resolveThread(comment.op.t)
  45. } catch (err) {
  46. debugConsole.error(err)
  47. showGenericMessageModal(
  48. t('resolve_comment_error_title'),
  49. t('resolve_comment_error_message')
  50. )
  51. } finally {
  52. setProcessing(false)
  53. }
  54. }, [comment.op.t, resolveThread, showGenericMessageModal, t])
  55. const handleEditMessage = useCallback(
  56. async (commentId: CommentId, content: string) => {
  57. setProcessing(true)
  58. try {
  59. await editMessage(comment.op.t, commentId, content)
  60. } catch (err) {
  61. debugConsole.error(err)
  62. showGenericMessageModal(
  63. t('edit_comment_error_title'),
  64. t('edit_comment_error_message')
  65. )
  66. } finally {
  67. setProcessing(false)
  68. }
  69. },
  70. [comment.op.t, editMessage, showGenericMessageModal, t]
  71. )
  72. const handleDeleteMessage = useCallback(
  73. async (commentId: CommentId) => {
  74. setProcessing(true)
  75. try {
  76. if (permissions.resolveAllComments) {
  77. // Owners and editors can delete any message
  78. await deleteMessage(comment.op.t, commentId)
  79. } else if (permissions.resolveOwnComments) {
  80. // Reviewers can only delete their own messages
  81. await deleteOwnMessage(comment.op.t, commentId)
  82. }
  83. } catch (err) {
  84. debugConsole.error(err)
  85. showGenericMessageModal(
  86. t('delete_comment_error_title'),
  87. t('delete_comment_error_message')
  88. )
  89. } finally {
  90. setProcessing(false)
  91. }
  92. },
  93. [
  94. comment.op.t,
  95. deleteMessage,
  96. deleteOwnMessage,
  97. showGenericMessageModal,
  98. t,
  99. permissions.resolveOwnComments,
  100. permissions.resolveAllComments,
  101. ]
  102. )
  103. const handleDeleteThread = useCallback(
  104. async (commentId: ThreadId) => {
  105. setProcessing(true)
  106. try {
  107. await deleteThread(commentId)
  108. } catch (err) {
  109. debugConsole.error(err)
  110. showGenericMessageModal(
  111. t('delete_comment_error_title'),
  112. t('delete_comment_error_message')
  113. )
  114. } finally {
  115. setProcessing(false)
  116. }
  117. },
  118. [deleteThread, showGenericMessageModal, t]
  119. )
  120. const handleSubmitReply = useCallback(
  121. async (content: string) => {
  122. setProcessing(true)
  123. try {
  124. await addMessage(comment.op.t, content)
  125. } catch (err) {
  126. debugConsole.error(err)
  127. showGenericMessageModal(
  128. t('add_comment_error_title'),
  129. t('add_comment_error_message')
  130. )
  131. throw err
  132. } finally {
  133. setProcessing(false)
  134. }
  135. },
  136. [addMessage, comment.op.t, showGenericMessageModal, t]
  137. )
  138. const { handleMouseEnter, handleMouseLeave } = useMemo(() => {
  139. return {
  140. handleMouseEnter: handleEnter && (() => handleEnter(comment.id)),
  141. handleMouseLeave: handleLeave && (() => handleLeave(comment.id)),
  142. }
  143. }, [comment.id, handleEnter, handleLeave])
  144. const thread = threads?.[comment.op.t]
  145. if (!thread || thread.resolved || thread.messages.length === 0) {
  146. return null
  147. }
  148. return (
  149. <ReviewPanelEntry
  150. className={classnames('review-panel-entry-comment', {
  151. 'review-panel-entry-loaded': !!threads?.[comment.op.t],
  152. 'review-panel-entry-hover': hovered,
  153. })}
  154. docId={docId}
  155. top={top}
  156. op={comment.op}
  157. position={comment.op.p}
  158. hoverRanges={hoverRanges}
  159. disabled={processing}
  160. handleEnter={handleMouseEnter}
  161. handleLeave={handleMouseLeave}
  162. entryIndicator="comment"
  163. >
  164. <ReviewPanelCommentContent
  165. comment={comment}
  166. isResolved={false}
  167. onLeave={handleMouseLeave}
  168. onEnter={handleMouseEnter}
  169. onResolve={handleResolveComment}
  170. onEdit={handleEditMessage}
  171. onDeleteMessage={handleDeleteMessage}
  172. onDeleteThread={handleDeleteThread}
  173. onReply={handleSubmitReply}
  174. />
  175. </ReviewPanelEntry>
  176. )
  177. }
  178. )
  179. ReviewPanelComment.displayName = 'ReviewPanelComment'