editor-context.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import {
  2. createContext,
  3. Dispatch,
  4. FC,
  5. SetStateAction,
  6. useCallback,
  7. useContext,
  8. useEffect,
  9. useMemo,
  10. useState,
  11. } from 'react'
  12. import useBrowserWindow from '../hooks/use-browser-window'
  13. import { useProjectContext } from './project-context'
  14. import { useDetachContext } from './detach-context'
  15. import getMeta from '../../utils/meta'
  16. import { useUserContext } from './user-context'
  17. import { saveProjectSettings } from '@/features/editor-left-menu/utils/api'
  18. import { useModalsContext } from '@/features/ide-react/context/modals-context'
  19. import { WritefullAPI } from './types/writefull-instance'
  20. import { Cobranding } from '../../../../types/cobranding'
  21. import { SymbolWithCharacter } from '../../../../modules/symbol-palette/frontend/js/data/symbols'
  22. export const EditorContext = createContext<
  23. | {
  24. cobranding?: Cobranding
  25. hasPremiumCompile?: boolean
  26. renameProject: (newName: string) => void
  27. insertSymbol?: (symbol: SymbolWithCharacter) => void
  28. isProjectOwner: boolean
  29. isRestrictedTokenMember?: boolean
  30. isPendingEditor: boolean
  31. deactivateTutorial: (tutorial: string) => void
  32. inactiveTutorials: string[]
  33. currentPopup: string | null
  34. setCurrentPopup: Dispatch<SetStateAction<string | null>>
  35. hasPremiumSuggestion: boolean
  36. setHasPremiumSuggestion: (value: boolean) => void
  37. setPremiumSuggestionResetDate: (date: Date) => void
  38. premiumSuggestionResetDate: Date
  39. writefullInstance: WritefullAPI | null
  40. setWritefullInstance: (instance: WritefullAPI) => void
  41. }
  42. | undefined
  43. >(undefined)
  44. export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
  45. const { id: userId, featureUsage } = useUserContext()
  46. const { role } = useDetachContext()
  47. const { showGenericMessageModal } = useModalsContext()
  48. const {
  49. features,
  50. projectId,
  51. project,
  52. name: projectName,
  53. updateProject,
  54. } = useProjectContext()
  55. const { owner, members } = project || {}
  56. const cobranding = useMemo(() => {
  57. const brandVariation = getMeta('ol-brandVariation')
  58. return (
  59. brandVariation && {
  60. logoImgUrl: brandVariation.logo_url,
  61. brandVariationName: brandVariation.name,
  62. brandVariationId: brandVariation.id,
  63. brandId: brandVariation.brand_id,
  64. brandVariationHomeUrl: brandVariation.home_url,
  65. publishGuideHtml: brandVariation.publish_guide_html,
  66. partner: brandVariation.partner,
  67. brandedMenu: brandVariation.branded_menu,
  68. submitBtnHtml: brandVariation.submit_button_html,
  69. submitBtnHtmlNoBreaks: brandVariation.submit_button_html_no_br,
  70. }
  71. )
  72. }, [])
  73. const [inactiveTutorials, setInactiveTutorials] = useState(
  74. () => getMeta('ol-inactiveTutorials') || []
  75. )
  76. const [currentPopup, setCurrentPopup] = useState<string | null>(null)
  77. const [hasPremiumSuggestion, setHasPremiumSuggestion] = useState<boolean>(
  78. () => {
  79. return Boolean(
  80. featureUsage?.aiErrorAssistant &&
  81. featureUsage?.aiErrorAssistant.remainingUsage > 0
  82. )
  83. }
  84. )
  85. const [premiumSuggestionResetDate, setPremiumSuggestionResetDate] =
  86. useState<Date>(() => {
  87. return featureUsage?.aiErrorAssistant?.resetDate
  88. ? new Date(featureUsage.aiErrorAssistant.resetDate)
  89. : new Date()
  90. })
  91. const isPendingEditor = useMemo(
  92. () =>
  93. Boolean(
  94. members?.some(
  95. member =>
  96. member._id === userId &&
  97. (member.pendingEditor || member.pendingReviewer)
  98. )
  99. ),
  100. [members, userId]
  101. )
  102. const deactivateTutorial = useCallback(
  103. (tutorialKey: string) => {
  104. setInactiveTutorials([...inactiveTutorials, tutorialKey])
  105. },
  106. [inactiveTutorials]
  107. )
  108. const renameProject = useCallback(
  109. (newName: string) => {
  110. const oldName = projectName
  111. if (newName !== oldName) {
  112. updateProject({ name: newName })
  113. saveProjectSettings(projectId, { name: newName }).catch(
  114. (response: any) => {
  115. updateProject({ name: oldName })
  116. const { data, status } = response
  117. showGenericMessageModal(
  118. 'Error renaming project',
  119. status === 400 ? data : 'Please try again in a moment'
  120. )
  121. }
  122. )
  123. }
  124. },
  125. [projectName, updateProject, projectId, showGenericMessageModal]
  126. )
  127. const { setTitle } = useBrowserWindow()
  128. useEffect(() => {
  129. const parts = []
  130. if (role === 'detached') {
  131. parts.push('[PDF]')
  132. }
  133. if (projectName) {
  134. parts.push(projectName)
  135. parts.push('-')
  136. }
  137. parts.push('Online LaTeX Editor')
  138. parts.push(getMeta('ol-ExposedSettings').appName)
  139. const title = parts.join(' ')
  140. setTitle(title)
  141. }, [projectName, setTitle, role])
  142. const insertSymbol = useCallback((symbol: SymbolWithCharacter) => {
  143. window.dispatchEvent(
  144. new CustomEvent('editor:insert-symbol', {
  145. detail: symbol,
  146. })
  147. )
  148. }, [])
  149. const [writefullInstance, setWritefullInstance] =
  150. useState<WritefullAPI | null>(null)
  151. const value = useMemo(
  152. () => ({
  153. cobranding,
  154. hasPremiumCompile: features?.compileGroup === 'priority',
  155. renameProject,
  156. isProjectOwner: owner?._id === userId,
  157. isRestrictedTokenMember: getMeta('ol-isRestrictedTokenMember'),
  158. isPendingEditor,
  159. insertSymbol,
  160. inactiveTutorials,
  161. deactivateTutorial,
  162. currentPopup,
  163. setCurrentPopup,
  164. hasPremiumSuggestion,
  165. setHasPremiumSuggestion,
  166. premiumSuggestionResetDate,
  167. setPremiumSuggestionResetDate,
  168. writefullInstance,
  169. setWritefullInstance,
  170. }),
  171. [
  172. cobranding,
  173. features?.compileGroup,
  174. owner,
  175. userId,
  176. renameProject,
  177. isPendingEditor,
  178. insertSymbol,
  179. inactiveTutorials,
  180. deactivateTutorial,
  181. currentPopup,
  182. setCurrentPopup,
  183. hasPremiumSuggestion,
  184. setHasPremiumSuggestion,
  185. premiumSuggestionResetDate,
  186. setPremiumSuggestionResetDate,
  187. writefullInstance,
  188. setWritefullInstance,
  189. ]
  190. )
  191. return (
  192. <EditorContext.Provider value={value}>{children}</EditorContext.Provider>
  193. )
  194. }
  195. export function useEditorContext() {
  196. const context = useContext(EditorContext)
  197. if (!context) {
  198. throw new Error('useEditorContext is only available inside EditorProvider')
  199. }
  200. return context
  201. }