editor-context.tsx 6.8 KB

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