editor-context.tsx 5.8 KB

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