track-changes-state-context.tsx 5.0 KB

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