settings-modal-context.tsx 8.7 KB

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