settings-modal-context.tsx 10 KB

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