editor-context.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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. import { useDetachContext } from './detach-context'
  14. export const EditorContext = createContext()
  15. EditorContext.Provider.propTypes = {
  16. value: PropTypes.shape({
  17. cobranding: PropTypes.shape({
  18. logoImgUrl: PropTypes.string.isRequired,
  19. brandVariationName: PropTypes.string.isRequired,
  20. brandVariationId: PropTypes.number.isRequired,
  21. brandId: PropTypes.number.isRequired,
  22. brandVariationHomeUrl: PropTypes.string.isRequired,
  23. publishGuideHtml: PropTypes.string,
  24. partner: PropTypes.string,
  25. brandedMenu: PropTypes.bool,
  26. submitBtnHtml: PropTypes.string,
  27. }),
  28. hasPremiumCompile: PropTypes.bool,
  29. loading: PropTypes.bool,
  30. renameProject: PropTypes.func.isRequired,
  31. showSymbolPalette: PropTypes.bool,
  32. toggleSymbolPalette: PropTypes.func,
  33. insertSymbol: PropTypes.func,
  34. showGalileo: PropTypes.bool,
  35. galileoActivated: PropTypes.bool,
  36. toggleGalileoPanel: PropTypes.func,
  37. toggleGalileo: PropTypes.func,
  38. insertGalileoAutocomplete: PropTypes.func,
  39. addGalileoReferenceToProject: PropTypes.func,
  40. isProjectOwner: PropTypes.bool,
  41. isRestrictedTokenMember: PropTypes.bool,
  42. permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
  43. }),
  44. }
  45. export function EditorProvider({ children, settings }) {
  46. const ide = useIdeContext()
  47. const { role } = useDetachContext()
  48. const { owner, features } = useProjectContext({
  49. owner: PropTypes.shape({
  50. _id: PropTypes.string.isRequired,
  51. }),
  52. features: PropTypes.shape({
  53. compileGroup: PropTypes.string,
  54. }),
  55. })
  56. const cobranding = useMemo(() => {
  57. if (window.brandVariation) {
  58. return {
  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. } else {
  70. return undefined
  71. }
  72. }, [])
  73. const [loading] = useScopeValue('state.loading')
  74. const [projectName, setProjectName] = useScopeValue('project.name')
  75. const [permissionsLevel] = useScopeValue('permissionsLevel')
  76. const [showSymbolPalette] = useScopeValue('editor.showSymbolPalette')
  77. const [toggleSymbolPalette] = useScopeValue('editor.toggleSymbolPalette')
  78. const [showGalileo] = useScopeValue('editor.showGalileo')
  79. const [galileoActivated] = useScopeValue('editor.galileoActivated')
  80. const [toggleGalileoPanel] = useScopeValue('editor.toggleGalileoPanel')
  81. const [toggleGalileo] = useScopeValue('editor.toggleGalileo')
  82. useEffect(() => {
  83. if (ide?.socket) {
  84. ide.socket.on('projectNameUpdated', setProjectName)
  85. return () =>
  86. ide.socket.removeListener('projectNameUpdated', setProjectName)
  87. }
  88. }, [ide?.socket, setProjectName])
  89. const renameProject = useCallback(
  90. newName => {
  91. setProjectName(oldName => {
  92. if (oldName !== newName) {
  93. settings.saveProjectSettings({ name: newName }).catch(response => {
  94. setProjectName(oldName)
  95. const { data, status } = response
  96. if (status === 400) {
  97. return ide.showGenericMessageModal('Error renaming project', data)
  98. } else {
  99. return ide.showGenericMessageModal(
  100. 'Error renaming project',
  101. 'Please try again in a moment'
  102. )
  103. }
  104. })
  105. }
  106. return newName
  107. })
  108. },
  109. [settings, ide, setProjectName]
  110. )
  111. const { setTitle } = useBrowserWindow()
  112. useEffect(() => {
  113. const parts = []
  114. if (role === 'detached') {
  115. parts.push('[PDF]')
  116. }
  117. if (projectName) {
  118. parts.push(projectName)
  119. parts.push('-')
  120. }
  121. parts.push('Online LaTeX Editor')
  122. parts.push(window.ExposedSettings.appName)
  123. const title = parts.join(' ')
  124. setTitle(title)
  125. }, [projectName, setTitle, role])
  126. const insertSymbol = useCallback(symbol => {
  127. window.dispatchEvent(
  128. new CustomEvent('editor:insert-symbol', {
  129. detail: symbol,
  130. })
  131. )
  132. }, [])
  133. const insertGalileoAutocomplete = useCallback(suggestion => {
  134. window.dispatchEvent(
  135. new CustomEvent('editor:insert-galileo-completion', {
  136. detail: suggestion,
  137. })
  138. )
  139. }, [])
  140. const addGalileoReferenceToProject = useCallback((citationKey, bibtex) => {
  141. window.dispatchEvent(
  142. new CustomEvent('editor:galileo-request-save-reference-in-project', {
  143. detail: { citationKey, bibtex },
  144. })
  145. )
  146. }, [])
  147. const value = useMemo(
  148. () => ({
  149. cobranding,
  150. hasPremiumCompile: features?.compileGroup === 'priority',
  151. loading,
  152. renameProject,
  153. permissionsLevel,
  154. isProjectOwner: owner?._id === window.user.id,
  155. isRestrictedTokenMember: window.isRestrictedTokenMember,
  156. showSymbolPalette,
  157. toggleSymbolPalette,
  158. insertSymbol,
  159. showGalileo,
  160. galileoActivated,
  161. toggleGalileoPanel,
  162. toggleGalileo,
  163. insertGalileoAutocomplete,
  164. addGalileoReferenceToProject,
  165. }),
  166. [
  167. cobranding,
  168. features?.compileGroup,
  169. loading,
  170. renameProject,
  171. permissionsLevel,
  172. owner?._id,
  173. showSymbolPalette,
  174. toggleSymbolPalette,
  175. insertSymbol,
  176. showGalileo,
  177. galileoActivated,
  178. toggleGalileoPanel,
  179. toggleGalileo,
  180. insertGalileoAutocomplete,
  181. addGalileoReferenceToProject,
  182. ]
  183. )
  184. return (
  185. <EditorContext.Provider value={value}>{children}</EditorContext.Provider>
  186. )
  187. }
  188. EditorProvider.propTypes = {
  189. children: PropTypes.any,
  190. settings: PropTypes.object,
  191. }
  192. export function useEditorContext(propTypes) {
  193. const context = useContext(EditorContext)
  194. if (!context) {
  195. throw new Error('useEditorContext is only available inside EditorProvider')
  196. }
  197. PropTypes.checkPropTypes(propTypes, context, 'data', 'EditorContext.Provider')
  198. return context
  199. }