editor-context.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {
  2. createContext,
  3. useCallback,
  4. useContext,
  5. useEffect,
  6. useMemo,
  7. } from 'react'
  8. import PropTypes from 'prop-types'
  9. import useScopeValue from '../hooks/use-scope-value'
  10. import useBrowserWindow from '../hooks/use-browser-window'
  11. import { useIdeContext } from './ide-context'
  12. import { useProjectContext } from './project-context'
  13. export const EditorContext = createContext()
  14. EditorContext.Provider.propTypes = {
  15. value: PropTypes.shape({
  16. cobranding: PropTypes.shape({
  17. logoImgUrl: PropTypes.string.isRequired,
  18. brandVariationName: PropTypes.string.isRequired,
  19. brandVariationId: PropTypes.number.isRequired,
  20. brandId: PropTypes.number.isRequired,
  21. brandVariationHomeUrl: PropTypes.string.isRequired,
  22. publishGuideHtml: PropTypes.string,
  23. partner: PropTypes.string,
  24. brandedMenu: PropTypes.bool,
  25. submitBtnHtml: PropTypes.string,
  26. }),
  27. hasPremiumCompile: PropTypes.bool,
  28. loading: PropTypes.bool,
  29. renameProject: PropTypes.func.isRequired,
  30. showSymbolPalette: PropTypes.bool,
  31. toggleSymbolPalette: PropTypes.func,
  32. insertSymbol: PropTypes.func,
  33. isProjectOwner: PropTypes.bool,
  34. isRestrictedTokenMember: PropTypes.bool,
  35. rootFolder: PropTypes.shape({
  36. children: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.string })),
  37. }),
  38. permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
  39. }),
  40. }
  41. export function EditorProvider({ children, settings }) {
  42. const ide = useIdeContext()
  43. const { owner, features } = useProjectContext({
  44. owner: PropTypes.shape({
  45. _id: PropTypes.string.isRequired,
  46. }),
  47. features: PropTypes.shape({
  48. compileGroup: PropTypes.string,
  49. }),
  50. })
  51. const cobranding = useMemo(() => {
  52. if (window.brandVariation) {
  53. return {
  54. logoImgUrl: window.brandVariation.logo_url,
  55. brandVariationName: window.brandVariation.name,
  56. brandVariationId: window.brandVariation.id,
  57. brandId: window.brandVariation.brand_id,
  58. brandVariationHomeUrl: window.brandVariation.home_url,
  59. publishGuideHtml: window.brandVariation.publish_guide_html,
  60. partner: window.brandVariation.partner,
  61. brandedMenu: window.brandVariation.branded_menu,
  62. submitBtnHtml: window.brandVariation.submit_button_html,
  63. }
  64. } else {
  65. return undefined
  66. }
  67. }, [])
  68. const [loading] = useScopeValue('state.loading')
  69. const [projectName, setProjectName] = useScopeValue('project.name')
  70. const [rootFolder] = useScopeValue('rootFolder', true)
  71. const [permissionsLevel] = useScopeValue('permissionsLevel')
  72. const [showSymbolPalette] = useScopeValue('editor.showSymbolPalette')
  73. const [toggleSymbolPalette] = useScopeValue('editor.toggleSymbolPalette')
  74. useEffect(() => {
  75. if (ide?.socket) {
  76. ide.socket.on('projectNameUpdated', setProjectName)
  77. return () =>
  78. ide.socket.removeListener('projectNameUpdated', setProjectName)
  79. }
  80. }, [ide?.socket, setProjectName])
  81. const renameProject = useCallback(
  82. newName => {
  83. setProjectName(oldName => {
  84. if (oldName !== newName) {
  85. settings.saveProjectSettings({ name: newName }).catch(response => {
  86. setProjectName(oldName)
  87. const { data, status } = response
  88. if (status === 400) {
  89. return ide.showGenericMessageModal('Error renaming project', data)
  90. } else {
  91. return ide.showGenericMessageModal(
  92. 'Error renaming project',
  93. 'Please try again in a moment'
  94. )
  95. }
  96. })
  97. }
  98. return newName
  99. })
  100. },
  101. [settings, ide, setProjectName]
  102. )
  103. const { setTitle } = useBrowserWindow()
  104. useEffect(() => {
  105. setTitle(
  106. `${projectName ? projectName + ' - ' : ''}Online LaTeX Editor ${
  107. window.ExposedSettings.appName
  108. }`
  109. )
  110. }, [projectName, setTitle])
  111. const insertSymbol = useCallback(symbol => {
  112. window.dispatchEvent(
  113. new CustomEvent('editor:insert-symbol', {
  114. detail: symbol,
  115. })
  116. )
  117. }, [])
  118. const value = useMemo(
  119. () => ({
  120. cobranding,
  121. hasPremiumCompile: features?.compileGroup === 'priority',
  122. loading,
  123. renameProject,
  124. permissionsLevel,
  125. isProjectOwner: owner?._id === window.user.id,
  126. isRestrictedTokenMember: window.isRestrictedTokenMember,
  127. rootFolder,
  128. showSymbolPalette,
  129. toggleSymbolPalette,
  130. insertSymbol,
  131. }),
  132. [
  133. cobranding,
  134. features?.compileGroup,
  135. loading,
  136. renameProject,
  137. permissionsLevel,
  138. owner?._id,
  139. rootFolder,
  140. showSymbolPalette,
  141. toggleSymbolPalette,
  142. insertSymbol,
  143. ]
  144. )
  145. return (
  146. <EditorContext.Provider value={value}>{children}</EditorContext.Provider>
  147. )
  148. }
  149. EditorProvider.propTypes = {
  150. children: PropTypes.any,
  151. settings: PropTypes.any.isRequired,
  152. }
  153. export function useEditorContext(propTypes) {
  154. const context = useContext(EditorContext)
  155. if (!context) {
  156. throw new Error('useEditorContext is only available inside EditorProvider')
  157. }
  158. PropTypes.checkPropTypes(propTypes, context, 'data', 'EditorContext.Provider')
  159. return context
  160. }