editor-context.tsx 6.4 KB

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