editor-context.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. showUpgradeModal: boolean
  42. setShowUpgradeModal: Dispatch<SetStateAction<boolean>>
  43. }
  44. | undefined
  45. >(undefined)
  46. export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
  47. const { id: userId, featureUsage } = useUserContext()
  48. const { role } = useDetachContext()
  49. const { showGenericMessageModal } = useModalsContext()
  50. const {
  51. features,
  52. projectId,
  53. project,
  54. name: projectName,
  55. updateProject,
  56. } = useProjectContext()
  57. const { owner, members } = project || {}
  58. const cobranding = useMemo(() => {
  59. const brandVariation = getMeta('ol-brandVariation')
  60. return (
  61. brandVariation && {
  62. logoImgUrl: brandVariation.logo_url,
  63. brandVariationName: brandVariation.name,
  64. brandVariationId: brandVariation.id,
  65. brandId: brandVariation.brand_id,
  66. brandVariationHomeUrl: brandVariation.home_url,
  67. publishGuideHtml: brandVariation.publish_guide_html,
  68. partner: brandVariation.partner,
  69. brandedMenu: brandVariation.branded_menu,
  70. submitBtnHtml: brandVariation.submit_button_html,
  71. submitBtnHtmlNoBreaks: brandVariation.submit_button_html_no_br,
  72. }
  73. )
  74. }, [])
  75. const [inactiveTutorials, setInactiveTutorials] = useState(
  76. () => getMeta('ol-inactiveTutorials') || []
  77. )
  78. const [currentPopup, setCurrentPopup] = useState<string | null>(null)
  79. const [hasPremiumSuggestion, setHasPremiumSuggestion] = useState<boolean>(
  80. () => {
  81. return Boolean(
  82. featureUsage?.aiErrorAssistant &&
  83. featureUsage?.aiErrorAssistant.remainingUsage > 0
  84. )
  85. }
  86. )
  87. const [premiumSuggestionResetDate, setPremiumSuggestionResetDate] =
  88. useState<Date>(() => {
  89. return featureUsage?.aiErrorAssistant?.resetDate
  90. ? new Date(featureUsage.aiErrorAssistant.resetDate)
  91. : new Date()
  92. })
  93. const [showUpgradeModal, setShowUpgradeModal] = useState(false)
  94. const isPendingEditor = useMemo(
  95. () =>
  96. Boolean(
  97. members?.some(
  98. member =>
  99. member._id === userId &&
  100. (member.pendingEditor || member.pendingReviewer)
  101. )
  102. ),
  103. [members, userId]
  104. )
  105. const deactivateTutorial = useCallback(
  106. (tutorialKey: string) => {
  107. setInactiveTutorials([...inactiveTutorials, tutorialKey])
  108. },
  109. [inactiveTutorials]
  110. )
  111. const renameProject = useCallback(
  112. (newName: string) => {
  113. const oldName = projectName
  114. if (newName !== oldName) {
  115. updateProject({ name: newName })
  116. saveProjectSettings(projectId, { name: newName }).catch(
  117. (response: any) => {
  118. updateProject({ name: oldName })
  119. const { data, status } = response
  120. showGenericMessageModal(
  121. 'Error renaming project',
  122. status === 400 ? data : 'Please try again in a moment'
  123. )
  124. }
  125. )
  126. }
  127. },
  128. [projectName, updateProject, projectId, showGenericMessageModal]
  129. )
  130. const { setTitle } = useBrowserWindow()
  131. useEffect(() => {
  132. const parts = []
  133. if (role === 'detached') {
  134. parts.push('[PDF]')
  135. }
  136. if (projectName) {
  137. parts.push(projectName)
  138. parts.push('-')
  139. }
  140. parts.push('Online LaTeX Editor')
  141. parts.push(getMeta('ol-ExposedSettings').appName)
  142. const title = parts.join(' ')
  143. setTitle(title)
  144. }, [projectName, setTitle, role])
  145. const insertSymbol = useCallback((symbol: SymbolWithCharacter) => {
  146. window.dispatchEvent(
  147. new CustomEvent('editor:insert-symbol', {
  148. detail: symbol,
  149. })
  150. )
  151. }, [])
  152. const [writefullInstance, setWritefullInstance] =
  153. useState<WritefullAPI | null>(null)
  154. const value = useMemo(
  155. () => ({
  156. cobranding,
  157. hasPremiumCompile: features?.compileGroup === 'priority',
  158. renameProject,
  159. isProjectOwner: owner?._id === userId,
  160. isRestrictedTokenMember: getMeta('ol-isRestrictedTokenMember'),
  161. isPendingEditor,
  162. insertSymbol,
  163. inactiveTutorials,
  164. deactivateTutorial,
  165. currentPopup,
  166. setCurrentPopup,
  167. hasPremiumSuggestion,
  168. setHasPremiumSuggestion,
  169. premiumSuggestionResetDate,
  170. setPremiumSuggestionResetDate,
  171. writefullInstance,
  172. setWritefullInstance,
  173. showUpgradeModal,
  174. setShowUpgradeModal,
  175. }),
  176. [
  177. cobranding,
  178. features?.compileGroup,
  179. owner,
  180. userId,
  181. renameProject,
  182. isPendingEditor,
  183. insertSymbol,
  184. inactiveTutorials,
  185. deactivateTutorial,
  186. currentPopup,
  187. setCurrentPopup,
  188. hasPremiumSuggestion,
  189. setHasPremiumSuggestion,
  190. premiumSuggestionResetDate,
  191. setPremiumSuggestionResetDate,
  192. writefullInstance,
  193. setWritefullInstance,
  194. showUpgradeModal,
  195. setShowUpgradeModal,
  196. ]
  197. )
  198. return (
  199. <EditorContext.Provider value={value}>{children}</EditorContext.Provider>
  200. )
  201. }
  202. export function useEditorContext() {
  203. const context = useContext(EditorContext)
  204. if (!context) {
  205. throw new Error('useEditorContext is only available inside EditorProvider')
  206. }
  207. return context
  208. }