ide-react-context.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {
  2. createContext,
  3. useContext,
  4. useState,
  5. FC,
  6. useMemo,
  7. useEffect,
  8. } from 'react'
  9. import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
  10. import { IdeProvider } from '@/shared/context/ide-context'
  11. import {
  12. createIdeEventEmitter,
  13. IdeEventEmitter,
  14. } from '@/features/ide-react/create-ide-event-emitter'
  15. import { JoinProjectPayload } from '@/features/ide-react/connection/join-project-payload'
  16. import { useConnectionContext } from '@/features/ide-react/context/connection-context'
  17. import { getMockIde } from '@/shared/context/mock/mock-ide'
  18. import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
  19. type IdeReactContextValue = {
  20. projectId: string
  21. eventEmitter: IdeEventEmitter
  22. }
  23. const IdeReactContext = createContext<IdeReactContextValue | null>(null)
  24. function populateIdeReactScope(store: ReactScopeValueStore) {
  25. store.set('sync_tex_error', false)
  26. }
  27. function populateProjectScope(store: ReactScopeValueStore) {
  28. store.allowNonExistentPath('project', true)
  29. store.set('permissionsLevel', 'readOnly')
  30. }
  31. function createReactScopeValueStore() {
  32. const scopeStore = new ReactScopeValueStore()
  33. // Populate the scope value store with default values that will be used by
  34. // nested contexts that refer to scope values. The ideal would be to leave
  35. // initialization of store values up to the nested context, which would keep
  36. // initialization code together with the context and would only populate
  37. // necessary values in the store, but this is simpler for now
  38. populateIdeReactScope(scopeStore)
  39. populateProjectScope(scopeStore)
  40. return scopeStore
  41. }
  42. const projectId = window.project_id
  43. export const IdeReactProvider: FC = ({ children }) => {
  44. const [scopeStore] = useState(createReactScopeValueStore)
  45. const [eventEmitter] = useState(createIdeEventEmitter)
  46. const [scopeEventEmitter] = useState(
  47. () => new ReactScopeEventEmitter(eventEmitter)
  48. )
  49. const { socket } = useConnectionContext()
  50. // Fire project:joined event
  51. useEffect(() => {
  52. function handleJoinProjectResponse({
  53. project,
  54. permissionsLevel,
  55. }: JoinProjectPayload) {
  56. eventEmitter.emit('project:joined', { project, permissionsLevel })
  57. }
  58. socket.on('joinProjectResponse', handleJoinProjectResponse)
  59. return () => {
  60. socket.removeListener('joinProjectResponse', handleJoinProjectResponse)
  61. }
  62. }, [socket, eventEmitter])
  63. // Populate scope values when joining project, then fire project:joined event
  64. useEffect(() => {
  65. function handleJoinProjectResponse({
  66. project,
  67. permissionsLevel,
  68. }: JoinProjectPayload) {
  69. scopeStore.set('project', { rootDoc_id: null, ...project })
  70. scopeStore.set('permissionsLevel', permissionsLevel)
  71. // Make watchers update immediately
  72. scopeStore.flushUpdates()
  73. eventEmitter.emit('project:joined', { project, permissionsLevel })
  74. }
  75. socket.on('joinProjectResponse', handleJoinProjectResponse)
  76. return () => {
  77. socket.removeListener('joinProjectResponse', handleJoinProjectResponse)
  78. }
  79. }, [socket, eventEmitter, scopeStore])
  80. const ide = useMemo(() => {
  81. return {
  82. ...getMockIde(),
  83. socket,
  84. }
  85. }, [socket])
  86. const value = useMemo(
  87. () => ({
  88. eventEmitter,
  89. projectId,
  90. }),
  91. [eventEmitter]
  92. )
  93. return (
  94. <IdeReactContext.Provider value={value}>
  95. <IdeProvider
  96. ide={ide}
  97. scopeStore={scopeStore}
  98. scopeEventEmitter={scopeEventEmitter}
  99. >
  100. {children}
  101. </IdeProvider>
  102. </IdeReactContext.Provider>
  103. )
  104. }
  105. export function useIdeReactContext(): IdeReactContextValue {
  106. const context = useContext(IdeReactContext)
  107. if (!context) {
  108. throw new Error(
  109. 'useIdeReactContext is only available inside IdeReactProvider'
  110. )
  111. }
  112. return context
  113. }