editor-providers.jsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // Disable prop type checks for test harnesses
  2. /* eslint-disable react/prop-types */
  3. import { merge } from 'lodash'
  4. import { SocketIOMock } from '@/ide/connection/SocketIoShim'
  5. import { IdeContext } from '@/shared/context/ide-context'
  6. import React, { useEffect, useState } from 'react'
  7. import {
  8. createReactScopeValueStore,
  9. IdeReactContext,
  10. } from '@/features/ide-react/context/ide-react-context'
  11. import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
  12. import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
  13. import { ConnectionContext } from '@/features/ide-react/context/connection-context'
  14. import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
  15. // these constants can be imported in tests instead of
  16. // using magic strings
  17. export const PROJECT_ID = 'project123'
  18. export const PROJECT_NAME = 'project-name'
  19. export const USER_ID = '123abd'
  20. export const USER_EMAIL = 'testuser@example.com'
  21. const defaultUserSettings = {
  22. pdfViewer: 'pdfjs',
  23. fontSize: 12,
  24. fontFamily: 'monaco',
  25. lineHeight: 'normal',
  26. editorTheme: 'textmate',
  27. overallTheme: '',
  28. mode: 'default',
  29. autoComplete: true,
  30. autoPairDelimiters: true,
  31. trackChanges: true,
  32. syntaxValidation: false,
  33. mathPreview: true,
  34. }
  35. export function EditorProviders({
  36. user = { id: USER_ID, email: USER_EMAIL },
  37. projectId = PROJECT_ID,
  38. projectOwner = {
  39. _id: '124abd',
  40. email: 'owner@example.com',
  41. },
  42. rootDocId = '_root_doc_id',
  43. imageName = 'texlive-full:2024.1',
  44. compiler = 'pdflatex',
  45. socket = new SocketIOMock(),
  46. isRestrictedTokenMember = false,
  47. scope: defaultScope = {},
  48. features = {
  49. referencesSearch: true,
  50. },
  51. projectFeatures = features,
  52. permissionsLevel = 'owner',
  53. children,
  54. rootFolder = [
  55. {
  56. _id: 'root-folder-id',
  57. name: 'rootFolder',
  58. docs: [
  59. {
  60. _id: '_root_doc_id',
  61. name: 'main.tex',
  62. },
  63. ],
  64. folders: [],
  65. fileRefs: [],
  66. },
  67. ],
  68. ui = { view: 'editor', pdfLayout: 'sideBySide', chatOpen: true },
  69. userSettings = {},
  70. providers = {},
  71. }) {
  72. window.metaAttributesCache.set(
  73. 'ol-gitBridgePublicBaseUrl',
  74. 'https://git.overleaf.test'
  75. )
  76. window.metaAttributesCache.set(
  77. 'ol-isRestrictedTokenMember',
  78. isRestrictedTokenMember
  79. )
  80. window.metaAttributesCache.set(
  81. 'ol-userSettings',
  82. merge({}, defaultUserSettings, userSettings)
  83. )
  84. const scope = merge(
  85. {
  86. user,
  87. editor: {
  88. sharejs_doc: {
  89. doc_id: 'test-doc',
  90. getSnapshot: () => 'some doc content',
  91. hasBufferedOps: () => false,
  92. on: () => {},
  93. off: () => {},
  94. leaveAndCleanUpPromise: async () => {},
  95. },
  96. },
  97. project: {
  98. _id: projectId,
  99. name: PROJECT_NAME,
  100. owner: projectOwner,
  101. features: projectFeatures,
  102. rootDocId,
  103. rootFolder,
  104. imageName,
  105. compiler,
  106. },
  107. ui,
  108. permissionsLevel,
  109. },
  110. defaultScope
  111. )
  112. // Add details for useUserContext
  113. window.metaAttributesCache.set('ol-user', { ...user, features })
  114. window.metaAttributesCache.set('ol-project_id', projectId)
  115. return (
  116. <ReactContextRoot
  117. providers={{
  118. ConnectionProvider: makeConnectionProvider(socket),
  119. IdeReactProvider: makeIdeReactProvider(scope, socket),
  120. ...providers,
  121. }}
  122. >
  123. {children}
  124. </ReactContextRoot>
  125. )
  126. }
  127. const makeConnectionProvider = socket => {
  128. const ConnectionProvider = ({ children }) => {
  129. const [value] = useState(() => ({
  130. socket,
  131. connectionState: {
  132. readyState: WebSocket.OPEN,
  133. forceDisconnected: false,
  134. inactiveDisconnect: false,
  135. reconnectAt: null,
  136. forcedDisconnectDelay: 0,
  137. lastConnectionAttempt: 0,
  138. error: '',
  139. },
  140. isConnected: true,
  141. isStillReconnecting: false,
  142. secondsUntilReconnect: () => 0,
  143. tryReconnectNow: () => {},
  144. registerUserActivity: () => {},
  145. disconnect: () => {},
  146. }))
  147. return (
  148. <ConnectionContext.Provider value={value}>
  149. {children}
  150. </ConnectionContext.Provider>
  151. )
  152. }
  153. return ConnectionProvider
  154. }
  155. const makeIdeReactProvider = (scope, socket) => {
  156. const IdeReactProvider = ({ children }) => {
  157. const [startedFreeTrial, setStartedFreeTrial] = useState(false)
  158. const [ideReactContextValue] = useState(() => ({
  159. projectId: PROJECT_ID,
  160. eventEmitter: new IdeEventEmitter(),
  161. startedFreeTrial,
  162. setStartedFreeTrial,
  163. reportError: () => {},
  164. projectJoined: true,
  165. }))
  166. const [ideContextValue] = useState(() => {
  167. const scopeStore = createReactScopeValueStore(PROJECT_ID)
  168. for (const [key, value] of Object.entries(scope)) {
  169. // TODO: path for nested entries
  170. scopeStore.set(key, value)
  171. }
  172. scopeStore.set('editor.sharejs_doc', scope.editor.sharejs_doc)
  173. scopeStore.set('ui.chatOpen', scope.ui.chatOpen)
  174. const scopeEventEmitter = new ReactScopeEventEmitter(
  175. new IdeEventEmitter()
  176. )
  177. return {
  178. socket,
  179. scopeStore,
  180. scopeEventEmitter,
  181. }
  182. })
  183. useEffect(() => {
  184. window.overleaf = {
  185. ...window.overleaf,
  186. unstable: {
  187. ...window.overleaf?.unstable,
  188. store: ideContextValue.scopeStore,
  189. },
  190. }
  191. }, [ideContextValue.scopeStore])
  192. return (
  193. <IdeReactContext.Provider value={ideReactContextValue}>
  194. <IdeContext.Provider value={ideContextValue}>
  195. {children}
  196. </IdeContext.Provider>
  197. </IdeReactContext.Provider>
  198. )
  199. }
  200. return IdeReactProvider
  201. }