editor-context.js 6.5 KB

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