| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- import {
- createContext,
- FC,
- useCallback,
- useContext,
- useMemo,
- useState,
- } from 'react'
- import { useLayoutContext } from '@/shared/context/layout-context'
- import AutoCloseBracketsSetting from '@/features/settings/components/editor-settings/auto-close-brackets-setting'
- import AutoCompleteSetting from '@/features/settings/components/editor-settings/auto-complete-setting'
- import CodeCheckSetting from '@/features/settings/components/editor-settings/code-check-setting'
- import PreviewTabsSetting from '@/features/settings/components/editor-settings/preview-tabs-setting'
- import KeybindingSetting from '@/features/settings/components/editor-settings/keybinding-setting'
- import PDFViewerSetting from '@/features/settings/components/editor-settings/pdf-viewer-setting'
- import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
- import SpellCheckSetting from '@/features/settings/components/editor-settings/spell-check-setting'
- import DictionarySetting from '@/features/settings/components/editor-settings/dictionary-setting'
- import { useTranslation } from 'react-i18next'
- import BreadcrumbsSetting from '@/features/settings/components/editor-settings/breadcrumbs-setting'
- import NonBlinkingCursorSetting from '@/features/settings/components/editor-settings/non-blinking-cursor-setting'
- import MathPreviewSetting from '@/features/settings/components/editor-settings/math-preview-setting'
- import RootDocumentSetting from '@/features/settings/components/compiler-settings/root-document-setting'
- import CompilerSetting from '@/features/settings/components/compiler-settings/compiler-setting'
- import ImageNameSetting from '@/features/settings/components/compiler-settings/image-name-setting'
- import DraftSetting from '@/features/settings/components/compiler-settings/draft-setting'
- import StopOnFirstErrorSetting from '@/features/settings/components/compiler-settings/stop-on-first-error-setting'
- import AutoCompileSetting from '@/features/settings/components/compiler-settings/auto-compile-setting'
- import OverallThemeSetting from '@/features/settings/components/appearance-settings/overall-theme-setting'
- import EditorThemeSetting from '@/features/settings/components/appearance-settings/editor-theme-setting'
- import FontSizeSetting from '@/features/settings/components/appearance-settings/font-size-setting'
- import LineHeightSetting from '@/features/settings/components/appearance-settings/line-height-setting'
- import FontFamilySetting from '@/features/settings/components/appearance-settings/font-family-setting'
- import DarkModePdfSetting from '@/features/settings/components/appearance-settings/dark-mode-pdf-setting'
- import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
- import { useFeatureFlag } from '@/shared/context/split-test-context'
- import ProjectNotificationsSetting from '@/features/settings/components/editor-settings/project-notifications-setting'
- import getMeta from '@/utils/meta'
- import type {
- SettingsEntry,
- SettingsSection,
- SettingsSectionHook,
- } from '@/features/settings/context/types'
- import EditorTabsSetting from '../components/editor-settings/editor-tabs-setting'
- import FloatingMenuSetting from '../components/editor-settings/floating-menu-setting'
- import useEventListener from '@/shared/hooks/use-event-listener'
- const [referenceSearchSettingModule] = importOverleafModules(
- 'referenceSearchSetting'
- )
- const ReferenceSearchSetting = referenceSearchSettingModule?.import.default
- const editorTabExtraSectionHooks: SettingsSectionHook[] = importOverleafModules(
- 'settingsModalEditorTabSections'
- )
- .map((m: any) => m?.import?.default)
- .filter((h: unknown): h is SettingsSectionHook => typeof h === 'function')
- const spellcheckExtraSectionHooks: SettingsSectionHook[] =
- importOverleafModules('settingsModalSpellcheckSections')
- .map((m: any) => m?.import?.default)
- .filter((h: unknown): h is SettingsSectionHook => typeof h === 'function')
- const useSlotSections = (hooks: SettingsSectionHook[]): SettingsSection[] =>
- hooks.map(hook => hook()).filter((s): s is SettingsSection => s != null)
- type SettingsModalState = {
- show: boolean
- setShow: (shown: boolean) => void
- activeTab: string | null | undefined
- setActiveTab: (tab: string | null | undefined) => void
- settingsTabs: SettingsEntry[]
- settingToTabMap: Map<string, string>
- settingToFocus?: string
- }
- export const SettingsModalContext = createContext<
- SettingsModalState | undefined
- >(undefined)
- export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
- children,
- }) => {
- const { t } = useTranslation()
- const { isOverleaf } = getMeta('ol-ExposedSettings')
- const { overallTheme, floatingMenu } = useProjectSettingsContext()
- const [settingToFocus, setSettingToFocus] = useState<string | undefined>(
- undefined
- )
- useEventListener(
- 'ui.focus-setting',
- useCallback((event: CustomEvent<string | undefined>) => {
- setSettingToFocus(event.detail)
- }, [])
- )
- const { settingsShown, setSettingsShown } = useLayoutContext()
- const hasEmailNotifications = useFeatureFlag('email-notifications')
- const hasEditorTabs = useFeatureFlag('editor-tabs')
- const hasToolbarMigration = useFeatureFlag('writefull-toolbar-migration')
- const editorTabExtraSections = useSlotSections(editorTabExtraSectionHooks)
- const spellcheckExtraSections = useSlotSections(spellcheckExtraSectionHooks)
- const allSettingsTabs: SettingsEntry[] = useMemo(
- () => [
- {
- key: 'editor',
- title: t('editor'),
- icon: 'code',
- sections: [
- {
- key: 'general',
- settings: [
- {
- key: 'autoComplete',
- component: <AutoCompleteSetting />,
- },
- {
- key: 'autoPairDelimiters',
- component: <AutoCloseBracketsSetting />,
- },
- {
- key: 'nonBlinkingCursor',
- component: <NonBlinkingCursorSetting />,
- },
- {
- key: 'syntaxValidation',
- component: <CodeCheckSetting />,
- },
- {
- key: 'editorTabs',
- component: <EditorTabsSetting />,
- hidden: !hasEditorTabs,
- },
- {
- key: 'previewTabs',
- component: <PreviewTabsSetting />,
- hidden: !hasEditorTabs,
- },
- {
- key: 'mode',
- component: <KeybindingSetting />,
- },
- {
- key: 'pdfViewer',
- component: <PDFViewerSetting />,
- },
- {
- key: 'write-and-cite-settings',
- component: <ReferenceSearchSetting />,
- hidden: !ReferenceSearchSetting,
- },
- {
- key: 'floating-menu',
- component: <FloatingMenuSetting />,
- hidden: !hasToolbarMigration && floatingMenu,
- },
- ],
- },
- {
- key: 'tools',
- title: t('tools'),
- settings: [
- {
- key: 'breadcrumbs-setting',
- component: <BreadcrumbsSetting />,
- },
- {
- key: 'mathPreview',
- component: <MathPreviewSetting />,
- },
- ],
- },
- ...editorTabExtraSections,
- ],
- },
- {
- key: 'spelling_and_language',
- title: t('spelling_and_language'),
- icon: 'spellcheck',
- sections: [
- {
- key: 'spellcheck',
- title: t('spellcheck'),
- settings: [
- {
- key: 'spellCheckLanguage',
- component: <SpellCheckSetting />,
- },
- {
- key: 'dictionary-settings',
- component: <DictionarySetting />,
- },
- ],
- },
- ...spellcheckExtraSections,
- ],
- },
- {
- key: 'compiler',
- title: t('compiler'),
- icon: 'picture_as_pdf',
- sections: [
- {
- key: 'general',
- settings: [
- {
- key: 'rootDocId',
- component: <RootDocumentSetting />,
- },
- {
- key: 'compiler',
- component: <CompilerSetting />,
- },
- {
- key: 'imageName',
- component: <ImageNameSetting />,
- },
- {
- key: 'draft',
- component: <DraftSetting />,
- },
- {
- key: 'stopOnFirstError',
- component: <StopOnFirstErrorSetting />,
- },
- {
- key: 'autoCompile',
- component: <AutoCompileSetting />,
- },
- ],
- },
- ],
- },
- {
- key: 'appearance',
- title: t('appearance'),
- icon: 'brush',
- sections: [
- {
- key: 'general',
- settings: [
- {
- key: 'overallTheme',
- component: <OverallThemeSetting />,
- },
- {
- key: 'editorTheme',
- component: <EditorThemeSetting />,
- },
- {
- key: 'pdfDarkMode',
- component: <DarkModePdfSetting />,
- hidden: overallTheme === 'light-',
- },
- {
- key: 'fontSize',
- component: <FontSizeSetting />,
- },
- {
- key: 'fontFamily',
- component: <FontFamilySetting />,
- },
- {
- key: 'lineHeight',
- component: <LineHeightSetting />,
- },
- ],
- },
- ],
- },
- {
- key: 'project_notifications',
- title: t('project_notifications'),
- icon: 'notifications' as const,
- sections: [
- {
- key: 'general',
- settings: [
- {
- key: 'projectNotifications',
- component: <ProjectNotificationsSetting />,
- },
- ],
- },
- ],
- hidden: !hasEmailNotifications,
- },
- {
- key: 'account_settings',
- title: t('account_settings'),
- icon: 'settings',
- href: '/user/settings',
- },
- {
- key: 'subscription',
- title: t('subscription'),
- icon: 'account_balance',
- href: '/user/subscription',
- hidden: !isOverleaf,
- },
- ],
- [
- t,
- hasEditorTabs,
- overallTheme,
- hasEmailNotifications,
- isOverleaf,
- editorTabExtraSections,
- spellcheckExtraSections,
- hasToolbarMigration,
- floatingMenu,
- ]
- )
- const settingsTabs = useMemo(
- () => allSettingsTabs.filter(tab => !tab.hidden),
- [allSettingsTabs]
- )
- const settingToTabMap = useMemo(() => {
- const map = new Map<string, string>()
- settingsTabs
- .filter(t => 'sections' in t)
- .forEach(tab => {
- tab.sections.forEach(section => {
- section.settings.forEach(setting => {
- map.set(setting.key, tab.key)
- })
- })
- })
- return map
- }, [settingsTabs])
- const [activeTab, setActiveTab] = useState<string | null | undefined>(
- settingsTabs[0]?.key
- )
- const value = useMemo(
- () => ({
- show: settingsShown,
- setShow: setSettingsShown,
- activeTab,
- setActiveTab,
- settingsTabs,
- settingToTabMap,
- settingToFocus,
- }),
- [
- settingsShown,
- setSettingsShown,
- activeTab,
- setActiveTab,
- settingsTabs,
- settingToTabMap,
- settingToFocus,
- ]
- )
- return (
- <SettingsModalContext.Provider value={value}>
- {children}
- </SettingsModalContext.Provider>
- )
- }
- export const useSettingsModalContext = () => {
- const value = useContext(SettingsModalContext)
- if (!value) {
- throw new Error(
- `useSettingsModalContext is only available inside SettingsModalProvider`
- )
- }
- return value
- }
|