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 settingToFocus?: string } export const SettingsModalContext = createContext< SettingsModalState | undefined >(undefined) export const SettingsModalProvider: FC = ({ children, }) => { const { t } = useTranslation() const { isOverleaf } = getMeta('ol-ExposedSettings') const { overallTheme, floatingMenu } = useProjectSettingsContext() const [settingToFocus, setSettingToFocus] = useState( undefined ) useEventListener( 'ui.focus-setting', useCallback((event: CustomEvent) => { 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: , }, { key: 'autoPairDelimiters', component: , }, { key: 'nonBlinkingCursor', component: , }, { key: 'syntaxValidation', component: , }, { key: 'editorTabs', component: , hidden: !hasEditorTabs, }, { key: 'previewTabs', component: , hidden: !hasEditorTabs, }, { key: 'mode', component: , }, { key: 'pdfViewer', component: , }, { key: 'write-and-cite-settings', component: , hidden: !ReferenceSearchSetting, }, { key: 'floating-menu', component: , hidden: !hasToolbarMigration && floatingMenu, }, ], }, { key: 'tools', title: t('tools'), settings: [ { key: 'breadcrumbs-setting', component: , }, { key: 'mathPreview', component: , }, ], }, ...editorTabExtraSections, ], }, { key: 'spelling_and_language', title: t('spelling_and_language'), icon: 'spellcheck', sections: [ { key: 'spellcheck', title: t('spellcheck'), settings: [ { key: 'spellCheckLanguage', component: , }, { key: 'dictionary-settings', component: , }, ], }, ...spellcheckExtraSections, ], }, { key: 'compiler', title: t('compiler'), icon: 'picture_as_pdf', sections: [ { key: 'general', settings: [ { key: 'rootDocId', component: , }, { key: 'compiler', component: , }, { key: 'imageName', component: , }, { key: 'draft', component: , }, { key: 'stopOnFirstError', component: , }, { key: 'autoCompile', component: , }, ], }, ], }, { key: 'appearance', title: t('appearance'), icon: 'brush', sections: [ { key: 'general', settings: [ { key: 'overallTheme', component: , }, { key: 'editorTheme', component: , }, { key: 'pdfDarkMode', component: , hidden: overallTheme === 'light-', }, { key: 'fontSize', component: , }, { key: 'fontFamily', component: , }, { key: 'lineHeight', component: , }, ], }, ], }, { key: 'project_notifications', title: t('project_notifications'), icon: 'notifications' as const, sections: [ { key: 'general', settings: [ { key: 'projectNotifications', component: , }, ], }, ], 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() 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( settingsTabs[0]?.key ) const value = useMemo( () => ({ show: settingsShown, setShow: setSettingsShown, activeTab, setActiveTab, settingsTabs, settingToTabMap, settingToFocus, }), [ settingsShown, setSettingsShown, activeTab, setActiveTab, settingsTabs, settingToTabMap, settingToFocus, ] ) return ( {children} ) } export const useSettingsModalContext = () => { const value = useContext(SettingsModalContext) if (!value) { throw new Error( `useSettingsModalContext is only available inside SettingsModalProvider` ) } return value }