review-panel-resolved-threads-menu.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import React, { FC, useMemo } from 'react'
  2. import { useThreadsContext } from '../context/threads-context'
  3. import { useTranslation } from 'react-i18next'
  4. import { ReviewPanelResolvedThread } from './review-panel-resolved-thread'
  5. import useProjectRanges from '../hooks/use-project-ranges'
  6. import { useFileTreeData } from '@/shared/context/file-tree-data-context'
  7. import { Change, CommentOperation } from '../../../../../types/change'
  8. import { ThreadId } from '../../../../../types/review-panel/review-panel'
  9. import LoadingSpinner from '@/shared/components/loading-spinner'
  10. import OLBadge from '@/shared/components/ol/ol-badge'
  11. import getMeta from '@/utils/meta'
  12. export const ReviewPanelResolvedThreadsMenu: FC = () => {
  13. const { t } = useTranslation()
  14. const threads = useThreadsContext()
  15. const { docs } = useFileTreeData()
  16. const { projectRanges, loading } = useProjectRanges()
  17. const docNameForThread = useMemo(() => {
  18. const docNameForThread = new Map<string, string>()
  19. const otMigrationStage = getMeta('ol-otMigrationStage')
  20. for (const [docId, ranges] of projectRanges?.entries() ?? []) {
  21. const docName = docs?.find(
  22. doc => (otMigrationStage === 1 ? doc.path : doc.doc.id) === docId
  23. )?.doc.name
  24. if (docName !== undefined) {
  25. for (const comment of ranges.comments) {
  26. const threadId = comment.op.t
  27. docNameForThread.set(threadId, docName)
  28. }
  29. }
  30. }
  31. return docNameForThread
  32. }, [docs, projectRanges])
  33. const allComments = useMemo(() => {
  34. const allComments = new Map<
  35. string,
  36. Change<CommentOperation> & { resolved?: boolean }
  37. >()
  38. // eslint-disable-next-line no-unused-vars
  39. for (const [_, ranges] of projectRanges?.entries() ?? []) {
  40. for (const comment of ranges.comments) {
  41. allComments.set(comment.op.t, comment)
  42. }
  43. }
  44. return allComments
  45. }, [projectRanges])
  46. const resolvedThreads = useMemo(() => {
  47. if (!threads) {
  48. return []
  49. }
  50. const allResolvedThreads = []
  51. for (const [id, thread] of Object.entries(threads)) {
  52. // sharejs-text-ot has "resolved" on the thread; history-ot has "resolved" on the comment
  53. if (thread.resolved || allComments.get(id)?.resolved) {
  54. allResolvedThreads.push({ thread, id })
  55. }
  56. }
  57. allResolvedThreads.sort((a, b) => {
  58. // TODO: add "resolved_at"/"resolved_by" to history-ot comments?
  59. if (!a.thread.resolved_at || !b.thread.resolved_at) {
  60. return 0
  61. }
  62. return Date.parse(b.thread.resolved_at) - Date.parse(a.thread.resolved_at)
  63. })
  64. return allResolvedThreads.filter(thread => allComments.has(thread.id))
  65. }, [threads, allComments])
  66. if (loading) {
  67. return <LoadingSpinner className="ms-auto me-auto" />
  68. }
  69. if (!resolvedThreads.length) {
  70. return (
  71. <div className="review-panel-resolved-comments-empty">
  72. {t('no_resolved_comments')}
  73. </div>
  74. )
  75. }
  76. return (
  77. <>
  78. <div className="review-panel-resolved-comments-header">
  79. <div className="review-panel-resolved-comments-label">
  80. {t('resolved_comments')}
  81. </div>
  82. <OLBadge
  83. bg="light"
  84. text="dark"
  85. className="review-panel-resolved-comments-count"
  86. >
  87. {resolvedThreads.length}
  88. </OLBadge>
  89. </div>
  90. {resolvedThreads.map(thread => {
  91. const comment = allComments.get(thread.id)
  92. if (!comment) {
  93. return null
  94. }
  95. return (
  96. <ReviewPanelResolvedThread
  97. key={thread.id}
  98. id={thread.id as ThreadId}
  99. comment={comment}
  100. docName={docNameForThread.get(thread.id) ?? t('unknown')}
  101. />
  102. )
  103. })}
  104. </>
  105. )
  106. }