track-changes-state-context.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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<React.PropsWithChildren> = ({
  42. children,
  43. }) => {
  44. const permissions = usePermissionsContext()
  45. const { socket } = useConnectionContext()
  46. const project = useProjectContext()
  47. const user = useUserContext()
  48. const { setWantTrackChanges } = useEditorManagerContext()
  49. // TODO: update project.trackChangesState instead?
  50. const [trackChangesValue, setTrackChangesValue] = useState<
  51. ProjectContextValue['trackChangesState']
  52. >(project.trackChangesState ?? false)
  53. useSocketListener(socket, 'toggle-track-changes', setTrackChangesValue)
  54. useEffect(() => {
  55. setWantTrackChanges(
  56. trackChangesValue === true ||
  57. (trackChangesValue !== false &&
  58. trackChangesValue[user.id ?? '__guests__'])
  59. )
  60. }, [setWantTrackChanges, trackChangesValue, user.id])
  61. const trackChangesIsObject =
  62. trackChangesValue !== true && trackChangesValue !== false
  63. const onForEveryone = trackChangesValue === true
  64. const onForGuests =
  65. onForEveryone ||
  66. (trackChangesIsObject && trackChangesValue.__guests__ === true)
  67. const onForMembers = useMemo(() => {
  68. const onForMembers: Record<UserId, boolean | undefined> = {}
  69. if (trackChangesIsObject) {
  70. for (const key of Object.keys(trackChangesValue)) {
  71. if (key !== '__guests__') {
  72. onForMembers[key as UserId] = trackChangesValue[key as UserId]
  73. }
  74. }
  75. }
  76. return onForMembers
  77. }, [trackChangesIsObject, trackChangesValue])
  78. const saveTrackChanges = useCallback(
  79. async (trackChangesBody: SaveTrackChangesRequestBody) => {
  80. postJSON(`/project/${project._id}/track_changes`, {
  81. body: trackChangesBody,
  82. })
  83. },
  84. [project._id]
  85. )
  86. const saveTrackChangesForCurrentUser = useCallback(
  87. async (trackChanges: boolean) => {
  88. if (user.id) {
  89. if (getMeta('ol-isReviewerRoleEnabled')) {
  90. saveTrackChanges({
  91. on_for: {
  92. ...onForMembers,
  93. [user.id]: trackChanges,
  94. },
  95. })
  96. } else {
  97. saveTrackChanges({
  98. on_for: {
  99. ...onForMembers,
  100. [user.id]: trackChanges,
  101. },
  102. on_for_guests: onForGuests,
  103. })
  104. }
  105. }
  106. },
  107. [onForMembers, onForGuests, user.id, saveTrackChanges]
  108. )
  109. const actions = useMemo(
  110. () => ({
  111. saveTrackChanges,
  112. saveTrackChangesForCurrentUser,
  113. }),
  114. [saveTrackChanges, saveTrackChangesForCurrentUser]
  115. )
  116. useEventListener(
  117. 'toggle-track-changes',
  118. useCallback(() => {
  119. if (
  120. user.id &&
  121. project.features.trackChanges &&
  122. permissions.write &&
  123. !onForEveryone
  124. ) {
  125. const value = onForMembers[user.id]
  126. if (getMeta('ol-isReviewerRoleEnabled')) {
  127. actions.saveTrackChanges({
  128. on_for: {
  129. ...onForMembers,
  130. [user.id]: !value,
  131. },
  132. })
  133. } else {
  134. actions.saveTrackChanges({
  135. on_for: {
  136. ...onForMembers,
  137. [user.id]: !value,
  138. },
  139. on_for_guests: onForGuests,
  140. })
  141. }
  142. }
  143. }, [
  144. actions,
  145. onForMembers,
  146. onForGuests,
  147. onForEveryone,
  148. permissions.write,
  149. project.features.trackChanges,
  150. user.id,
  151. ])
  152. )
  153. const value = useMemo(
  154. () => ({ onForEveryone, onForGuests, onForMembers }),
  155. [onForEveryone, onForGuests, onForMembers]
  156. )
  157. return (
  158. <TrackChangesStateActionsContext.Provider value={actions}>
  159. <TrackChangesStateContext.Provider value={value}>
  160. {children}
  161. </TrackChangesStateContext.Provider>
  162. </TrackChangesStateActionsContext.Provider>
  163. )
  164. }
  165. export const useTrackChangesStateContext = () => {
  166. return useContext(TrackChangesStateContext)
  167. }
  168. export const useTrackChangesStateActionsContext = () => {
  169. const context = useContext(TrackChangesStateActionsContext)
  170. if (!context) {
  171. throw new Error(
  172. 'useTrackChangesStateActionsContext is only available inside TrackChangesStateProvider'
  173. )
  174. }
  175. return context
  176. }