track-changes-state-context.tsx 5.0 KB

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