overview-file.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import { useMemo, useRef } from 'react'
  2. import Icon from '../../../../shared/components/icon'
  3. import ChangeEntry from './entries/change-entry'
  4. import AggregateChangeEntry from './entries/aggregate-change-entry'
  5. import CommentEntry from './entries/comment-entry'
  6. import {
  7. useReviewPanelUpdaterFnsContext,
  8. useReviewPanelValueContext,
  9. } from '../../context/review-panel/review-panel-context'
  10. import classnames from 'classnames'
  11. import { ThreadId } from '../../../../../../types/review-panel/review-panel'
  12. import { MainDocument } from '../../../../../../types/project-settings'
  13. import { ReviewPanelEntry } from '../../../../../../types/review-panel/entry'
  14. import useCollapseHeight from './hooks/use-collapse-height'
  15. type OverviewFileProps = {
  16. docId: MainDocument['doc']['id']
  17. docPath: MainDocument['path']
  18. }
  19. function OverviewFile({ docId, docPath }: OverviewFileProps) {
  20. const { entries, collapsed, commentThreads, permissions, users } =
  21. useReviewPanelValueContext()
  22. const { setCollapsed } = useReviewPanelUpdaterFnsContext()
  23. const docCollapsed = collapsed[docId]
  24. const docEntries = useMemo(
  25. () => (docId in entries ? entries[docId] : {}),
  26. [docId, entries]
  27. )
  28. const objectEntries = useMemo(() => {
  29. const entries = Object.entries(docEntries) as Array<
  30. [ThreadId, ReviewPanelEntry]
  31. >
  32. const orderedEntries = entries.sort(([, entryA], [, entryB]) => {
  33. return entryA.offset - entryB.offset
  34. })
  35. return orderedEntries
  36. }, [docEntries])
  37. const entryCount = Object.keys(docEntries).length
  38. const handleToggleCollapsed = () => {
  39. setCollapsed({ ...collapsed, [docId]: !docCollapsed })
  40. }
  41. const entriesContainerRef = useRef<HTMLDivElement | null>(null)
  42. useCollapseHeight(entriesContainerRef, docCollapsed)
  43. return (
  44. <div className="rp-overview-file">
  45. {entryCount > 0 && (
  46. // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
  47. <div
  48. className="rp-overview-file-header"
  49. onClick={handleToggleCollapsed}
  50. >
  51. <span
  52. className={classnames('rp-overview-file-header-collapse', {
  53. 'rp-overview-file-header-collapse-on': docCollapsed,
  54. })}
  55. >
  56. <Icon type="angle-down" />
  57. </span>
  58. {docPath}
  59. {docCollapsed && (
  60. <>
  61. &nbsp;
  62. <span className="rp-overview-file-num-entries">
  63. ({entryCount})
  64. </span>
  65. </>
  66. )}
  67. </div>
  68. )}
  69. <div className="rp-overview-file-entries" ref={entriesContainerRef}>
  70. {objectEntries.map(([id, entry]) => {
  71. if (entry.type === 'insert' || entry.type === 'delete') {
  72. return (
  73. <ChangeEntry
  74. key={id}
  75. docId={docId}
  76. entryId={id}
  77. permissions={permissions}
  78. user={users[entry.metadata.user_id]}
  79. content={entry.content}
  80. offset={entry.offset}
  81. type={entry.type}
  82. focused={entry.focused}
  83. entryIds={entry.entry_ids}
  84. timestamp={entry.metadata.ts}
  85. />
  86. )
  87. }
  88. if (entry.type === 'aggregate-change') {
  89. return (
  90. <AggregateChangeEntry
  91. key={id}
  92. docId={docId}
  93. entryId={id}
  94. permissions={permissions}
  95. user={users[entry.metadata.user_id]}
  96. content={entry.content}
  97. replacedContent={entry.metadata.replaced_content}
  98. offset={entry.offset}
  99. focused={entry.focused}
  100. entryIds={entry.entry_ids}
  101. timestamp={entry.metadata.ts}
  102. />
  103. )
  104. }
  105. if (entry.type === 'comment') {
  106. const thread = commentThreads[entry.thread_id]
  107. if (!thread?.resolved) {
  108. return (
  109. <CommentEntry
  110. key={id}
  111. docId={docId}
  112. threadId={entry.thread_id}
  113. thread={thread}
  114. entryId={id}
  115. offset={entry.offset}
  116. focused={entry.focused}
  117. permissions={permissions}
  118. />
  119. )
  120. }
  121. }
  122. return null
  123. })}
  124. </div>
  125. </div>
  126. )
  127. }
  128. export default OverviewFile