settings-modal-context.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { createContext, FC, useContext, useMemo, useState } from 'react'
  2. import { useLayoutContext } from '@/shared/context/layout-context'
  3. import AutoCloseBracketsSetting from '../components/settings/editor-settings/auto-close-brackets-setting'
  4. import AutoCompleteSetting from '../components/settings/editor-settings/auto-complete-setting'
  5. import CodeCheckSetting from '../components/settings/editor-settings/code-check-setting'
  6. import KeybindingSetting from '../components/settings/editor-settings/keybinding-setting'
  7. import PDFViewerSetting from '../components/settings/editor-settings/pdf-viewer-setting'
  8. import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
  9. import SpellCheckSetting from '../components/settings/editor-settings/spell-check-setting'
  10. import DictionarySetting from '../components/settings/editor-settings/dictionary-setting'
  11. import { useTranslation } from 'react-i18next'
  12. import BreadcrumbsSetting from '../components/settings/editor-settings/breadcrumbs-setting'
  13. import MathPreviewSetting from '../components/settings/editor-settings/math-preview-setting'
  14. import RootDocumentSetting from '../components/settings/compiler-settings/root-document-setting'
  15. import CompilerSetting from '../components/settings/compiler-settings/compiler-setting'
  16. import ImageNameSetting from '../components/settings/compiler-settings/image-name-setting'
  17. import DraftSetting from '../components/settings/compiler-settings/draft-setting'
  18. import StopOnFirstErrorSetting from '../components/settings/compiler-settings/stop-on-first-error-setting'
  19. import AutoCompileSetting from '../components/settings/compiler-settings/auto-compile-setting'
  20. import OverallThemeSetting from '../components/settings/appearance-settings/overall-theme-setting'
  21. import EditorThemeSetting from '../components/settings/appearance-settings/editor-theme-setting'
  22. import FontSizeSetting from '../components/settings/appearance-settings/font-size-setting'
  23. import LineHeightSetting from '../components/settings/appearance-settings/line-height-setting'
  24. import FontFamilySetting from '../components/settings/appearance-settings/font-family-setting'
  25. import { AvailableUnfilledIcon } from '@/shared/components/material-icon'
  26. import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
  27. import NewEditorSetting from '../components/settings/editor-settings/new-editor-setting'
  28. import { canUseNewEditorViaNewUserFeatureFlag } from '../utils/new-editor-utils'
  29. const [referenceSearchSettingModule] = importOverleafModules(
  30. 'referenceSearchSetting'
  31. )
  32. const ReferenceSearchSetting = referenceSearchSettingModule?.import.default
  33. type Setting = {
  34. key: string
  35. component: React.ReactNode
  36. hidden?: boolean
  37. }
  38. type SettingsSection = {
  39. title?: string
  40. key: string
  41. settings: Setting[]
  42. }
  43. export type SettingsTab = {
  44. key: string
  45. icon: AvailableUnfilledIcon
  46. sections: SettingsSection[]
  47. title: string
  48. }
  49. type SettingsLink = {
  50. key: string
  51. icon: AvailableUnfilledIcon
  52. href: string
  53. title: string
  54. }
  55. export type SettingsEntry = SettingsLink | SettingsTab
  56. type SettingsModalState = {
  57. show: boolean
  58. setShow: (shown: boolean) => void
  59. activeTab: string | null | undefined
  60. setActiveTab: (tab: string | null | undefined) => void
  61. settingsTabs: SettingsEntry[]
  62. settingToTabMap: Map<string, string>
  63. }
  64. export const SettingsModalContext = createContext<
  65. SettingsModalState | undefined
  66. >(undefined)
  67. export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
  68. children,
  69. }) => {
  70. const { t } = useTranslation()
  71. const showEditorSwitch = canUseNewEditorViaNewUserFeatureFlag()
  72. // TODO ide-redesign-cleanup: Rename this field and move it directly into this context
  73. const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
  74. const settingsTabs: SettingsEntry[] = useMemo(
  75. () => [
  76. {
  77. key: 'editor',
  78. title: t('editor'),
  79. icon: 'code',
  80. sections: [
  81. {
  82. key: 'general',
  83. settings: [
  84. {
  85. key: 'autoComplete',
  86. component: <AutoCompleteSetting />,
  87. },
  88. {
  89. key: 'autoPairDelimiters',
  90. component: <AutoCloseBracketsSetting />,
  91. },
  92. {
  93. key: 'syntaxValidation',
  94. component: <CodeCheckSetting />,
  95. },
  96. {
  97. key: 'mode',
  98. component: <KeybindingSetting />,
  99. },
  100. {
  101. key: 'pdfViewer',
  102. component: <PDFViewerSetting />,
  103. },
  104. {
  105. key: 'write-and-cite-settings',
  106. component: <ReferenceSearchSetting />,
  107. hidden: !ReferenceSearchSetting,
  108. },
  109. ],
  110. },
  111. {
  112. key: 'spellcheck',
  113. title: t('spellcheck'),
  114. settings: [
  115. {
  116. key: 'spellCheckLanguage',
  117. component: <SpellCheckSetting />,
  118. },
  119. {
  120. key: 'dictionary-settings',
  121. component: <DictionarySetting />,
  122. },
  123. ],
  124. },
  125. {
  126. key: 'tools',
  127. title: t('tools'),
  128. settings: [
  129. {
  130. key: 'breadcrumbs-setting',
  131. component: <BreadcrumbsSetting />,
  132. },
  133. {
  134. key: 'mathPreview',
  135. component: <MathPreviewSetting />,
  136. },
  137. ],
  138. },
  139. ],
  140. },
  141. {
  142. key: 'compiler',
  143. title: t('compiler'),
  144. icon: 'picture_as_pdf',
  145. sections: [
  146. {
  147. key: 'general',
  148. settings: [
  149. {
  150. key: 'rootDocId',
  151. component: <RootDocumentSetting />,
  152. },
  153. {
  154. key: 'compiler',
  155. component: <CompilerSetting />,
  156. },
  157. {
  158. key: 'imageName',
  159. component: <ImageNameSetting />,
  160. },
  161. {
  162. key: 'draft',
  163. component: <DraftSetting />,
  164. },
  165. {
  166. key: 'stopOnFirstError',
  167. component: <StopOnFirstErrorSetting />,
  168. },
  169. {
  170. key: 'autoCompile',
  171. component: <AutoCompileSetting />,
  172. },
  173. ],
  174. },
  175. ],
  176. },
  177. {
  178. key: 'appearance',
  179. title: t('appearance'),
  180. icon: 'brush',
  181. sections: [
  182. {
  183. key: 'general',
  184. settings: [
  185. {
  186. key: 'overallTheme',
  187. component: <OverallThemeSetting />,
  188. },
  189. {
  190. key: 'editorTheme',
  191. component: <EditorThemeSetting />,
  192. },
  193. {
  194. key: 'fontSize',
  195. component: <FontSizeSetting />,
  196. },
  197. {
  198. key: 'fontFamily',
  199. component: <FontFamilySetting />,
  200. },
  201. {
  202. key: 'lineHeight',
  203. component: <LineHeightSetting />,
  204. },
  205. {
  206. key: 'newEditor',
  207. component: <NewEditorSetting />,
  208. hidden: !showEditorSwitch,
  209. },
  210. ],
  211. },
  212. ],
  213. },
  214. {
  215. key: 'account_settings',
  216. title: t('account_settings'),
  217. icon: 'settings',
  218. href: '/user/settings',
  219. },
  220. {
  221. key: 'subscription',
  222. title: t('subscription'),
  223. icon: 'account_balance',
  224. href: '/user/subscription',
  225. },
  226. ],
  227. [t, showEditorSwitch]
  228. )
  229. const settingToTabMap = useMemo(() => {
  230. const map = new Map<string, string>()
  231. settingsTabs
  232. .filter(t => 'sections' in t)
  233. .forEach(tab => {
  234. tab.sections.forEach(section => {
  235. section.settings.forEach(setting => {
  236. map.set(setting.key, tab.key)
  237. })
  238. })
  239. })
  240. return map
  241. }, [settingsTabs])
  242. const [activeTab, setActiveTab] = useState<string | null | undefined>(
  243. settingsTabs[0]?.key
  244. )
  245. const value = useMemo(
  246. () => ({
  247. show: leftMenuShown,
  248. setShow: setLeftMenuShown,
  249. activeTab,
  250. setActiveTab,
  251. settingsTabs,
  252. settingToTabMap,
  253. }),
  254. [
  255. leftMenuShown,
  256. setLeftMenuShown,
  257. activeTab,
  258. setActiveTab,
  259. settingsTabs,
  260. settingToTabMap,
  261. ]
  262. )
  263. return (
  264. // TODO ide-redesign-cleanup: Merge <EditorLeftMenuProvider> into <SettingsModalProvider>
  265. <EditorLeftMenuProvider>
  266. <SettingsModalContext.Provider value={value}>
  267. {children}
  268. </SettingsModalContext.Provider>
  269. </EditorLeftMenuProvider>
  270. )
  271. }
  272. export const useSettingsModalContext = () => {
  273. const value = useContext(SettingsModalContext)
  274. if (!value) {
  275. throw new Error(
  276. `useSettingsModalContext is only available inside SettingsModalProvider`
  277. )
  278. }
  279. return value
  280. }