track-changes-state-context.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { UserId } from '../../../../../types/user'
  2. import {
  3. createContext,
  4. FC,
  5. useCallback,
  6. useContext,
  7. useEffect,
  8. useMemo,
  9. useState,
  10. } from 'react'
  11. import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
  12. import { useConnectionContext } from '@/features/ide-react/context/connection-context'
  13. import { useProjectContext } from '@/shared/context/project-context'
  14. import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
  15. import { useUserContext } from '@/shared/context/user-context'
  16. import { postJSON } from '@/infrastructure/fetch-json'
  17. import useEventListener from '@/shared/hooks/use-event-listener'
  18. import { ProjectContextValue } from '@/shared/context/types/project-context'
  19. import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
  20. import getMeta from '@/utils/meta'
  21. export type TrackChangesState = {
  22. onForEveryone: boolean
  23. onForGuests: boolean
  24. onForMembers: Record<UserId, boolean | undefined>
  25. }
  26. export const TrackChangesStateContext = createContext<
  27. TrackChangesState | undefined
  28. >(undefined)
  29. type SaveTrackChangesRequestBody = {
  30. on?: boolean
  31. on_for?: Record<UserId, boolean | undefined>
  32. on_for_guests?: boolean
  33. }
  34. type TrackChangesStateActions = {
  35. saveTrackChanges: (trackChangesBody: SaveTrackChangesRequestBody) => void
  36. saveTrackChangesForCurrentUser: (trackChanges: boolean) => void
  37. }
  38. const TrackChangesStateActionsContext = createContext<
  39. TrackChangesStateActions | undefined
  40. >(undefined)
  41. export const TrackChangesStateProvider: FC = ({ children }) => {
  42. const permissions = usePermissionsContext()
  43. const { socket } = useConnectionContext()
  44. const project = useProjectContext()
  45. const user = useUserContext()
  46. const { setWantTrackChanges } = useEditorManagerContext()
  47. // TODO: update project.trackChangesState instead?
  48. const [trackChangesValue, setTrackChangesValue] = useState<
  49. ProjectContextValue['trackChangesState']
  50. >(project.trackChangesState ?? false)
  51. useSocketListener(socket, 'toggle-track-changes', setTrackChangesValue)
  52. useEffect(() => {
  53. setWantTrackChanges(
  54. trackChangesValue === true ||
  55. (trackChangesValue !== false &&
  56. trackChangesValue[user.id ?? '__guests__'])
  57. )
  58. }, [setWantTrackChanges, trackChangesValue, user.id])
  59. const trackChangesIsObject =
  60. trackChangesValue !== true && trackChangesValue !== false
  61. const onForEveryone = trackChangesValue === true
  62. const onForGuests =
  63. onForEveryone ||
  64. (trackChangesIsObject && trackChangesValue.__guests__ === true)
  65. const onForMembers = useMemo(() => {
  66. const onForMembers: Record<UserId, boolean | undefined> = {}
  67. if (trackChangesIsObject) {
  68. for (const key of Object.keys(trackChangesValue)) {
  69. if (key !== '__guests__') {
  70. onForMembers[key as UserId] = trackChangesValue[key as UserId]
  71. }
  72. }
  73. }
  74. return onForMembers
  75. }, [trackChangesIsObject, trackChangesValue])
  76. const saveTrackChanges = useCallback(
  77. async (trackChangesBody: SaveTrackChangesRequestBody) => {
  78. postJSON(`/project/${project._id}/track_changes`, {
  79. body: trackChangesBody,
  80. })
  81. },
  82. [project._id]
  83. )
  84. const saveTrackChangesForCurrentUser = useCallback(
  85. async (trackChanges: boolean) => {
  86. if (user.id) {
  87. if (getMeta('ol-isReviewerRoleEnabled')) {
  88. saveTrackChanges({
  89. on_for: {
  90. ...onForMembers,
  91. [user.id]: trackChanges,
  92. },
  93. })
  94. } else {
  95. saveTrackChanges({
  96. on_for: {
  97. ...onForMembers,
  98. [user.id]: trackChanges,
  99. },
  100. on_for_guests: onForGuests,
  101. })
  102. }
  103. }
  104. },
  105. [onForMembers, onForGuests, user.id, saveTrackChanges]
  106. )
  107. const actions = useMemo(
  108. () => ({
  109. saveTrackChanges,
  110. saveTrackChangesForCurrentUser,
  111. }),
  112. [saveTrackChanges, saveTrackChangesForCurrentUser]
  113. )
  114. useEventListener(
  115. 'toggle-track-changes',
  116. useCallback(() => {
  117. if (
  118. user.id &&
  119. project.features.trackChanges &&
  120. permissions.write &&
  121. !onForEveryone
  122. ) {
  123. const value = onForMembers[user.id]
  124. if (getMeta('ol-isReviewerRoleEnabled')) {
  125. actions.saveTrackChanges({
  126. on_for: {
  127. ...onForMembers,
  128. [user.id]: !value,
  129. },
  130. })
  131. } else {
  132. actions.saveTrackChanges({
  133. on_for: {
  134. ...onForMembers,
  135. [user.id]: !value,
  136. },
  137. on_for_guests: onForGuests,
  138. })
  139. }
  140. }
  141. }, [
  142. actions,
  143. onForMembers,
  144. onForGuests,
  145. onForEveryone,
  146. permissions.write,
  147. project.features.trackChanges,
  148. user.id,
  149. ])
  150. )
  151. const value = useMemo(
  152. () => ({ onForEveryone, onForGuests, onForMembers }),
  153. [onForEveryone, onForGuests, onForMembers]
  154. )
  155. return (
  156. <TrackChangesStateActionsContext.Provider value={actions}>
  157. <TrackChangesStateContext.Provider value={value}>
  158. {children}
  159. </TrackChangesStateContext.Provider>
  160. </TrackChangesStateActionsContext.Provider>
  161. )
  162. }
  163. export const useTrackChangesStateContext = () => {
  164. return useContext(TrackChangesStateContext)
  165. }
  166. export const useTrackChangesStateActionsContext = () => {
  167. const context = useContext(TrackChangesStateActionsContext)
  168. if (!context) {
  169. throw new Error(
  170. 'useTrackChangesStateActionsContext is only available inside TrackChangesStateProvider'
  171. )
  172. }
  173. return context
  174. }