track-changes-state-context.tsx 4.9 KB

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