mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #15707 from overleaf/td-user-settings-context
Move user settings to a context GitOrigin-RevId: 9a9d55dfee9f71cee323fe64d1442303ac7cfeb2
This commit is contained in:
parent
4f13470345
commit
38efea39f2
25 changed files with 291 additions and 202 deletions
|
@ -1,11 +1,11 @@
|
|||
import { memo, useEffect, useRef, useState } from 'react'
|
||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
|
||||
export default memo(function LeftMenuMask() {
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const [editorTheme] = useScopeValue('settings.editorTheme')
|
||||
const [overallTheme] = useScopeValue('settings.overallTheme')
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const { editorTheme, overallTheme } = userSettings
|
||||
const [original] = useState({ editorTheme, overallTheme })
|
||||
const maskRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import { useProjectSettingsContext } from '../../context/project-settings-contex
|
|||
import SettingsMenuSelect from './settings-menu-select'
|
||||
import type { Option } from './settings-menu-select'
|
||||
|
||||
const sizes = ['10', '11', '12', '13', '14', '16', '18', '20', '22', '24']
|
||||
const options: Array<Option> = sizes.map(size => ({
|
||||
const sizes = [10, 11, 12, 13, 14, 16, 18, 20, 22, 24]
|
||||
const options: Option<number>[] = sizes.map(size => ({
|
||||
value: size,
|
||||
label: `${size}px`,
|
||||
}))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ChangeEventHandler, useCallback } from 'react'
|
||||
|
||||
type PossibleValue = string | boolean
|
||||
type PossibleValue = string | number | boolean
|
||||
|
||||
export type Option<T extends PossibleValue = string> = {
|
||||
value: T
|
||||
|
@ -35,14 +35,16 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
|
|||
}: SettingsMenuSelectProps<T>) {
|
||||
const handleChange: ChangeEventHandler<HTMLSelectElement> = useCallback(
|
||||
event => {
|
||||
let value: PossibleValue = event.target.value
|
||||
if (value === 'true' || value === 'false') {
|
||||
value = value === 'true'
|
||||
const selectedValue = event.target.value
|
||||
let onChangeValue: PossibleValue = selectedValue
|
||||
if (typeof value === 'boolean') {
|
||||
onChangeValue = selectedValue === 'true'
|
||||
} else if (typeof value === 'number') {
|
||||
onChangeValue = parseInt(selectedValue, 10)
|
||||
}
|
||||
|
||||
onChange(value as T)
|
||||
onChange(onChangeValue as T)
|
||||
},
|
||||
[onChange]
|
||||
[onChange, value]
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,7 +2,8 @@ import { createContext, FC, useContext, useMemo } from 'react'
|
|||
import useProjectWideSettings from '../hooks/use-project-wide-settings'
|
||||
import useUserWideSettings from '../hooks/use-user-wide-settings'
|
||||
import useProjectWideSettingsSocketListener from '../hooks/use-project-wide-settings-socket-listener'
|
||||
import type { ProjectSettings, UserSettings } from '../utils/api'
|
||||
import type { ProjectSettings } from '../utils/api'
|
||||
import { UserSettings } from '../../../../../types/user-settings'
|
||||
|
||||
type ProjectSettingsSetterContextValue = {
|
||||
setCompiler: (compiler: ProjectSettings['compiler']) => void
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
import { saveUserSettings } from '../utils/api'
|
||||
import type { UserSettings } from '../utils/api'
|
||||
import { UserSettings } from '../../../../../types/user-settings'
|
||||
|
||||
export default function useSaveUserSettings() {
|
||||
const [userSettings, setUserSettings] = useScopeValue<UserSettings>(
|
||||
'settings',
|
||||
true
|
||||
)
|
||||
const { userSettings, setUserSettings } = useUserSettingsContext()
|
||||
|
||||
return (
|
||||
key: keyof UserSettings,
|
||||
|
|
|
@ -2,16 +2,25 @@ import { useCallback, useEffect, useState } from 'react'
|
|||
import _ from 'lodash'
|
||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import type { OverallThemeMeta } from '../../../../../types/project-settings'
|
||||
import { saveUserSettings, type UserSettings } from '../utils/api'
|
||||
import { saveUserSettings } from '../utils/api'
|
||||
import { UserSettings } from '../../../../../types/user-settings'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
|
||||
export default function useSetOverallTheme() {
|
||||
const [chosenTheme, setChosenTheme] = useState<OverallThemeMeta | null>(null)
|
||||
const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue<boolean>(
|
||||
'ui.loadingStyleSheet'
|
||||
)
|
||||
const [overallTheme, setOverallTheme] = useScopeValue<
|
||||
UserSettings['overallTheme']
|
||||
>('settings.overallTheme')
|
||||
|
||||
const { userSettings, setUserSettings } = useUserSettingsContext()
|
||||
const { overallTheme } = userSettings
|
||||
|
||||
const setOverallTheme = useCallback(
|
||||
(overallTheme: UserSettings['overallTheme']) => {
|
||||
setUserSettings(settings => ({ ...settings, overallTheme }))
|
||||
},
|
||||
[setUserSettings]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const docHeadEl = document.querySelector('head')
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import { useCallback } from 'react'
|
||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
import useSetOverallTheme from './use-set-overall-theme'
|
||||
import useSaveUserSettings from './use-save-user-settings'
|
||||
import type { UserSettings } from '../utils/api'
|
||||
import { UserSettings } from '../../../../../types/user-settings'
|
||||
|
||||
export default function useUserWideSettings() {
|
||||
const saveUserSettings = useSaveUserSettings()
|
||||
|
||||
// this may be undefined on test environments
|
||||
const [userSettings] = useScopeValue<UserSettings | undefined>(
|
||||
'settings',
|
||||
true
|
||||
)
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const {
|
||||
overallTheme,
|
||||
autoComplete,
|
||||
autoPairDelimiters,
|
||||
syntaxValidation,
|
||||
editorTheme,
|
||||
mode,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
lineHeight,
|
||||
pdfViewer,
|
||||
} = userSettings
|
||||
|
||||
const setOverallTheme = useSetOverallTheme()
|
||||
const setAutoComplete = useCallback(
|
||||
|
@ -78,25 +86,25 @@ export default function useUserWideSettings() {
|
|||
)
|
||||
|
||||
return {
|
||||
autoComplete: userSettings?.autoComplete,
|
||||
autoComplete,
|
||||
setAutoComplete,
|
||||
autoPairDelimiters: userSettings?.autoPairDelimiters,
|
||||
autoPairDelimiters,
|
||||
setAutoPairDelimiters,
|
||||
syntaxValidation: userSettings?.syntaxValidation,
|
||||
syntaxValidation,
|
||||
setSyntaxValidation,
|
||||
editorTheme: userSettings?.editorTheme,
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
overallTheme: userSettings?.overallTheme,
|
||||
overallTheme,
|
||||
setOverallTheme,
|
||||
mode: userSettings?.mode,
|
||||
mode,
|
||||
setMode,
|
||||
fontSize: userSettings?.fontSize,
|
||||
fontSize,
|
||||
setFontSize,
|
||||
fontFamily: userSettings?.fontFamily,
|
||||
fontFamily,
|
||||
setFontFamily,
|
||||
lineHeight: userSettings?.lineHeight,
|
||||
lineHeight,
|
||||
setLineHeight,
|
||||
pdfViewer: userSettings?.pdfViewer,
|
||||
pdfViewer,
|
||||
setPdfViewer,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,8 @@
|
|||
import type {
|
||||
FontFamily,
|
||||
LineHeight,
|
||||
OverallTheme,
|
||||
} from '../../source-editor/extensions/theme'
|
||||
import type {
|
||||
Keybindings,
|
||||
PdfViewer,
|
||||
ProjectCompiler,
|
||||
} from '../../../../../types/project-settings'
|
||||
import type { ProjectCompiler } from '../../../../../types/project-settings'
|
||||
import { sendMB } from '../../../infrastructure/event-tracking'
|
||||
import { postJSON } from '../../../infrastructure/fetch-json'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
export type UserSettings = {
|
||||
pdfViewer: PdfViewer
|
||||
autoComplete: boolean
|
||||
autoPairDelimiters: boolean
|
||||
syntaxValidation: boolean
|
||||
editorTheme: string
|
||||
overallTheme: OverallTheme
|
||||
mode: Keybindings
|
||||
fontSize: string
|
||||
fontFamily: FontFamily
|
||||
lineHeight: LineHeight
|
||||
}
|
||||
import { UserSettings } from '../../../../../types/user-settings'
|
||||
|
||||
export type ProjectSettings = {
|
||||
compiler: ProjectCompiler
|
||||
|
|
|
@ -8,14 +8,8 @@ import {
|
|||
import { EditorView, lineNumbers } from '@codemirror/view'
|
||||
import { indentationMarkers } from '@replit/codemirror-indentation-markers'
|
||||
import { highlights, setHighlightsEffect } from '../../extensions/highlights'
|
||||
import useScopeValue from '../../../../shared/hooks/use-scope-value'
|
||||
import {
|
||||
theme,
|
||||
Options,
|
||||
setOptionsTheme,
|
||||
FontFamily,
|
||||
LineHeight,
|
||||
} from '../../extensions/theme'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
import { theme, Options, setOptionsTheme } from '../../extensions/theme'
|
||||
import { indentUnit } from '@codemirror/language'
|
||||
import { Highlight } from '../../services/types/doc'
|
||||
import useIsMounted from '../../../../shared/hooks/use-is-mounted'
|
||||
|
@ -50,9 +44,8 @@ function DocumentDiffViewer({
|
|||
doc: string
|
||||
highlights: Highlight[]
|
||||
}) {
|
||||
const [fontFamily] = useScopeValue<FontFamily>('settings.fontFamily')
|
||||
const [fontSize] = useScopeValue<number>('settings.fontSize')
|
||||
const [lineHeight] = useScopeValue<LineHeight>('settings.lineHeight')
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const { fontFamily, fontSize, lineHeight } = userSettings
|
||||
const isMounted = useIsMounted()
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import { getMockIde } from '@/shared/context/mock/mock-ide'
|
|||
import { populateEditorScope } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { EventLog } from '@/features/ide-react/editor/event-log'
|
||||
import { populateSettingsScope } from '@/features/ide-react/scope-adapters/settings-adapter'
|
||||
import { populateOnlineUsersScope } from '@/features/ide-react/context/online-users-context'
|
||||
import { populateReferenceScope } from '@/features/ide-react/context/references-context'
|
||||
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
|
||||
|
@ -69,7 +68,6 @@ function createReactScopeValueStore(projectId: string) {
|
|||
populateLayoutScope(scopeStore)
|
||||
populateProjectScope(scopeStore)
|
||||
populatePdfScope(scopeStore)
|
||||
populateSettingsScope(scopeStore)
|
||||
populateOnlineUsersScope(scopeStore)
|
||||
populateReferenceScope(scopeStore)
|
||||
populateReviewPanelScope(scopeStore)
|
||||
|
|
|
@ -18,6 +18,7 @@ import { ReferencesProvider } from '@/features/ide-react/context/references-cont
|
|||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import { ModalsContextProvider } from '@/features/ide-react/context/modals-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
|
||||
|
||||
export const ReactContextRoot: FC = ({ children }) => {
|
||||
return (
|
||||
|
@ -25,37 +26,39 @@ export const ReactContextRoot: FC = ({ children }) => {
|
|||
<ConnectionProvider>
|
||||
<IdeReactProvider>
|
||||
<UserProvider>
|
||||
<ProjectProvider>
|
||||
<FileTreeDataProvider>
|
||||
<FileTreePathProvider>
|
||||
<ReferencesProvider>
|
||||
<DetachProvider>
|
||||
<EditorProvider>
|
||||
<ProjectSettingsProvider>
|
||||
<LayoutProvider>
|
||||
<LocalCompileProvider>
|
||||
<DetachCompileProvider>
|
||||
<ChatProvider>
|
||||
<ModalsContextProvider>
|
||||
<EditorManagerProvider>
|
||||
<OnlineUsersProvider>
|
||||
<MetadataProvider>
|
||||
{children}
|
||||
</MetadataProvider>
|
||||
</OnlineUsersProvider>
|
||||
</EditorManagerProvider>
|
||||
</ModalsContextProvider>
|
||||
</ChatProvider>
|
||||
</DetachCompileProvider>
|
||||
</LocalCompileProvider>
|
||||
</LayoutProvider>
|
||||
</ProjectSettingsProvider>
|
||||
</EditorProvider>
|
||||
</DetachProvider>
|
||||
</ReferencesProvider>
|
||||
</FileTreePathProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
<UserSettingsProvider>
|
||||
<ProjectProvider>
|
||||
<FileTreeDataProvider>
|
||||
<FileTreePathProvider>
|
||||
<ReferencesProvider>
|
||||
<DetachProvider>
|
||||
<EditorProvider>
|
||||
<ProjectSettingsProvider>
|
||||
<LayoutProvider>
|
||||
<LocalCompileProvider>
|
||||
<DetachCompileProvider>
|
||||
<ChatProvider>
|
||||
<ModalsContextProvider>
|
||||
<EditorManagerProvider>
|
||||
<OnlineUsersProvider>
|
||||
<MetadataProvider>
|
||||
{children}
|
||||
</MetadataProvider>
|
||||
</OnlineUsersProvider>
|
||||
</EditorManagerProvider>
|
||||
</ModalsContextProvider>
|
||||
</ChatProvider>
|
||||
</DetachCompileProvider>
|
||||
</LocalCompileProvider>
|
||||
</LayoutProvider>
|
||||
</ProjectSettingsProvider>
|
||||
</EditorProvider>
|
||||
</DetachProvider>
|
||||
</ReferencesProvider>
|
||||
</FileTreePathProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
</UserSettingsProvider>
|
||||
</UserProvider>
|
||||
</IdeReactProvider>
|
||||
</ConnectionProvider>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
|
||||
|
||||
export function populateSettingsScope(store: ReactScopeValueStore) {
|
||||
store.set('settings', window.userSettings)
|
||||
}
|
|
@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import Tooltip from '../../../shared/components/tooltip'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import classnames from 'classnames'
|
||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
import { getStoredSelection, setStoredSelection } from '../extensions/search'
|
||||
import { debounce } from 'lodash'
|
||||
import { EditorSelection, EditorState } from '@codemirror/state'
|
||||
|
@ -46,8 +46,8 @@ const CodeMirrorSearchForm: FC = () => {
|
|||
const view = useCodeMirrorViewContext()
|
||||
const state = useCodeMirrorStateContext()
|
||||
|
||||
const [keybindings] = useScopeValue<string>('settings.mode')
|
||||
const emacsKeybindingsActive = keybindings === 'emacs'
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const emacsKeybindingsActive = userSettings.mode === 'emacs'
|
||||
const [activeSearchOption, setActiveSearchOption] =
|
||||
useState<ActiveSearchOption>(null)
|
||||
|
||||
|
|
|
@ -6,10 +6,7 @@ import useEventListener from '../../../shared/hooks/use-event-listener'
|
|||
import useScopeEventListener from '../../../shared/hooks/use-scope-event-listener'
|
||||
import { createExtensions } from '../extensions'
|
||||
import {
|
||||
FontFamily,
|
||||
LineHeight,
|
||||
lineHeights,
|
||||
OverallTheme,
|
||||
setEditorTheme,
|
||||
setOptionsTheme,
|
||||
} from '../extensions/theme'
|
||||
|
@ -49,6 +46,7 @@ import { useErrorHandler } from 'react-error-boundary'
|
|||
import { setVisual } from '../extensions/visual/visual'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
|
||||
function useCodeMirrorScope(view: EditorView) {
|
||||
const ide = useIdeContext()
|
||||
|
@ -67,17 +65,18 @@ function useCodeMirrorScope(view: EditorView) {
|
|||
const [docName] = useScopeValue<string>('editor.open_doc_name')
|
||||
const [trackChanges] = useScopeValue<boolean>('editor.trackChanges')
|
||||
|
||||
const [fontFamily] = useScopeValue<FontFamily>('settings.fontFamily')
|
||||
const [fontSize] = useScopeValue<number>('settings.fontSize')
|
||||
const [lineHeight] = useScopeValue<LineHeight>('settings.lineHeight')
|
||||
const [overallTheme] = useScopeValue<OverallTheme>('settings.overallTheme')
|
||||
const [autoComplete] = useScopeValue<boolean>('settings.autoComplete')
|
||||
const [editorTheme] = useScopeValue<string>('settings.editorTheme')
|
||||
const [autoPairDelimiters] = useScopeValue<boolean>(
|
||||
'settings.autoPairDelimiters'
|
||||
)
|
||||
const [mode] = useScopeValue<string>('settings.mode')
|
||||
const [syntaxValidation] = useScopeValue<boolean>('settings.syntaxValidation')
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
lineHeight,
|
||||
overallTheme,
|
||||
autoComplete,
|
||||
editorTheme,
|
||||
autoPairDelimiters,
|
||||
mode,
|
||||
syntaxValidation,
|
||||
} = userSettings
|
||||
|
||||
const [cursorHighlights] = useScopeValue<Record<string, Highlight[]>>(
|
||||
'onlineUserCursorHighlights'
|
||||
|
|
|
@ -367,12 +367,9 @@ If the project has been renamed please look in your project list for a new proje
|
|||
'vibrant_ink',
|
||||
]
|
||||
$scope.darkTheme = false
|
||||
$scope.$watch('settings.editorTheme', function (theme) {
|
||||
if (Array.from(DARK_THEMES).includes(theme)) {
|
||||
return ($scope.darkTheme = true)
|
||||
} else {
|
||||
return ($scope.darkTheme = false)
|
||||
}
|
||||
// Listen for settings change from React
|
||||
window.addEventListener('settings:change', event => {
|
||||
$scope.darkTheme = DARK_THEMES.includes(event.detail.editorTheme)
|
||||
})
|
||||
|
||||
ide.localStorage = localStorage
|
||||
|
|
|
@ -31,6 +31,7 @@ import { useLayoutContext } from './layout-context'
|
|||
import { useUserContext } from './user-context'
|
||||
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
|
||||
export const LocalCompileContext = createContext()
|
||||
|
||||
|
@ -114,8 +115,9 @@ export function LocalCompileProvider({ children }) {
|
|||
'pdf.logEntryAnnotations'
|
||||
)
|
||||
|
||||
// the PDF viewer
|
||||
const [pdfViewer] = useScopeValue('settings.pdfViewer')
|
||||
// the PDF viewer and whether syntax validation is enabled globally
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const { pdfViewer, syntaxValidation } = userSettings
|
||||
|
||||
// the URL for downloading the PDF
|
||||
const [, setPdfDownloadUrl] = useScopeValueSetterOnly('pdf.downloadUrl')
|
||||
|
@ -232,9 +234,6 @@ export function LocalCompileProvider({ children }) {
|
|||
// whether the editor linter found errors
|
||||
const [hasLintingError, setHasLintingError] = useScopeValue('hasLintingError')
|
||||
|
||||
// whether syntax validation is enabled globally
|
||||
const [syntaxValidation] = useScopeValue('settings.syntaxValidation')
|
||||
|
||||
// the timestamp that a doc was last changed
|
||||
const [changedAt, setChangedAt] = useState(0)
|
||||
|
||||
|
|
|
@ -14,31 +14,34 @@ import { SplitTestProvider } from './split-test-context'
|
|||
import { FileTreeDataProvider } from './file-tree-data-context'
|
||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
|
||||
|
||||
export function ContextRoot({ children, ide }) {
|
||||
return (
|
||||
<SplitTestProvider>
|
||||
<IdeAngularProvider ide={ide}>
|
||||
<UserProvider>
|
||||
<ProjectProvider>
|
||||
<FileTreeDataProvider>
|
||||
<FileTreePathProvider>
|
||||
<DetachProvider>
|
||||
<EditorProvider>
|
||||
<ProjectSettingsProvider>
|
||||
<LayoutProvider>
|
||||
<LocalCompileProvider>
|
||||
<DetachCompileProvider>
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</DetachCompileProvider>
|
||||
</LocalCompileProvider>
|
||||
</LayoutProvider>
|
||||
</ProjectSettingsProvider>
|
||||
</EditorProvider>
|
||||
</DetachProvider>
|
||||
</FileTreePathProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
<UserSettingsProvider>
|
||||
<ProjectProvider>
|
||||
<FileTreeDataProvider>
|
||||
<FileTreePathProvider>
|
||||
<DetachProvider>
|
||||
<EditorProvider>
|
||||
<ProjectSettingsProvider>
|
||||
<LayoutProvider>
|
||||
<LocalCompileProvider>
|
||||
<DetachCompileProvider>
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</DetachCompileProvider>
|
||||
</LocalCompileProvider>
|
||||
</LayoutProvider>
|
||||
</ProjectSettingsProvider>
|
||||
</EditorProvider>
|
||||
</DetachProvider>
|
||||
</FileTreePathProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
</UserSettingsProvider>
|
||||
</UserProvider>
|
||||
</IdeAngularProvider>
|
||||
</SplitTestProvider>
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useMemo,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
FC,
|
||||
useState,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
|
||||
import { UserSettings } from '../../../../types/user-settings'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
const defaultSettings: UserSettings = {
|
||||
pdfViewer: 'pdfjs',
|
||||
autoComplete: true,
|
||||
autoPairDelimiters: true,
|
||||
syntaxValidation: false,
|
||||
editorTheme: 'textmate',
|
||||
overallTheme: '',
|
||||
mode: 'default',
|
||||
fontSize: 12,
|
||||
fontFamily: 'monaco',
|
||||
lineHeight: 'normal',
|
||||
}
|
||||
|
||||
type UserSettingsContextValue = {
|
||||
userSettings: UserSettings
|
||||
setUserSettings: Dispatch<
|
||||
SetStateAction<UserSettingsContextValue['userSettings']>
|
||||
>
|
||||
}
|
||||
|
||||
export const UserSettingsContext = createContext<
|
||||
UserSettingsContextValue | undefined
|
||||
>(undefined)
|
||||
|
||||
export const UserSettingsProvider: FC = ({ children }) => {
|
||||
const [userSettings, setUserSettings] = useState<
|
||||
UserSettingsContextValue['userSettings']
|
||||
>(() => getMeta('ol-userSettings') || defaultSettings)
|
||||
|
||||
const value = useMemo<UserSettingsContextValue>(
|
||||
() => ({
|
||||
userSettings,
|
||||
setUserSettings,
|
||||
}),
|
||||
[userSettings, setUserSettings]
|
||||
)
|
||||
|
||||
// Fire an event to inform non-React code of settings changes
|
||||
useEffect(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('settings:change', { detail: userSettings })
|
||||
)
|
||||
}, [userSettings])
|
||||
|
||||
return (
|
||||
<UserSettingsContext.Provider value={value}>
|
||||
{children}
|
||||
</UserSettingsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useUserSettingsContext() {
|
||||
const context = useContext(UserSettingsContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useUserSettingsContext is only available inside UserSettingsProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
|
@ -231,12 +231,12 @@ describe('<PdfPreview/>', function () {
|
|||
|
||||
const scope = mockScope()
|
||||
// enable linting in the editor
|
||||
scope.settings.syntaxValidation = true
|
||||
const userSettings = { syntaxValidation: true }
|
||||
// mock a linting error
|
||||
scope.hasLintingError = true
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} userSettings={userSettings}>
|
||||
<div className="pdf-viewer">
|
||||
<PdfPreview />
|
||||
</div>
|
||||
|
|
|
@ -140,11 +140,11 @@ contentLine3
|
|||
\\end{document}`
|
||||
|
||||
const scope = mockScope(shortDoc)
|
||||
scope.settings.mode = 'emacs'
|
||||
const userSettings = { mode: 'emacs' }
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} userSettings={userSettings}>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
@ -245,11 +245,11 @@ contentLine3
|
|||
`
|
||||
|
||||
const scope = mockScope(shortDoc)
|
||||
scope.settings.mode = 'vim'
|
||||
const userSettings = { mode: 'vim' }
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} userSettings={userSettings}>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
|
|
@ -41,13 +41,13 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
|
|||
|
||||
it('renders client-side lint annotations in the gutter', function () {
|
||||
const scope = mockScope()
|
||||
scope.settings.syntaxValidation = true
|
||||
const userSettings = { syntaxValidation: true }
|
||||
|
||||
cy.clock()
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} userSettings={userSettings}>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
@ -89,11 +89,13 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
|
|||
],
|
||||
}
|
||||
|
||||
const userSettings = { syntaxValidation: false }
|
||||
|
||||
cy.clock()
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} userSettings={userSettings}>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
@ -318,11 +320,11 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
|
|||
cy.interceptCompile()
|
||||
|
||||
const scope = mockScope()
|
||||
scope.settings.mode = 'vim'
|
||||
const userSettings = { mode: 'vim' }
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} userSettings={userSettings}>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
|
|
@ -7,18 +7,6 @@ export const figuresFolderId = '123456789012345678901234'
|
|||
export const figureId = '234567890123456789012345'
|
||||
export const mockScope = (content?: string) => {
|
||||
return {
|
||||
settings: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'monaco',
|
||||
lineHeight: 'normal',
|
||||
editorTheme: 'textmate',
|
||||
overallTheme: '',
|
||||
mode: 'default',
|
||||
autoComplete: true,
|
||||
autoPairDelimiters: true,
|
||||
trackChanges: true,
|
||||
syntaxValidation: false,
|
||||
},
|
||||
editor: {
|
||||
sharejs_doc: mockDoc(content),
|
||||
open_doc_name: 'test.tex',
|
||||
|
|
|
@ -14,6 +14,7 @@ import { LocalCompileProvider } from '@/shared/context/local-compile-context'
|
|||
import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
|
||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
|
||||
|
||||
// these constants can be imported in tests instead of
|
||||
// using magic strings
|
||||
|
@ -22,6 +23,20 @@ export const PROJECT_NAME = 'project-name'
|
|||
export const USER_ID = '123abd'
|
||||
export const USER_EMAIL = 'testuser@example.com'
|
||||
|
||||
const defaultUserSettings = {
|
||||
pdfViewer: 'pdfjs',
|
||||
fontSize: 12,
|
||||
fontFamily: 'monaco',
|
||||
lineHeight: 'normal',
|
||||
editorTheme: 'textmate',
|
||||
overallTheme: '',
|
||||
mode: 'default',
|
||||
autoComplete: true,
|
||||
autoPairDelimiters: true,
|
||||
trackChanges: true,
|
||||
syntaxValidation: false,
|
||||
}
|
||||
|
||||
export function EditorProviders({
|
||||
user = { id: USER_ID, email: USER_EMAIL },
|
||||
projectId = PROJECT_ID,
|
||||
|
@ -71,12 +86,17 @@ export function EditorProviders({
|
|||
},
|
||||
},
|
||||
},
|
||||
userSettings = {},
|
||||
providers = {},
|
||||
}) {
|
||||
window.user = user || window.user
|
||||
window.gitBridgePublicBaseUrl = 'https://git.overleaf.test'
|
||||
window.project_id = projectId != null ? projectId : window.project_id
|
||||
window.isRestrictedTokenMember = isRestrictedTokenMember
|
||||
window.metaAttributesCache.set(
|
||||
'ol-userSettings',
|
||||
merge({}, defaultUserSettings, userSettings)
|
||||
)
|
||||
|
||||
const $scope = merge(
|
||||
{
|
||||
|
@ -126,6 +146,7 @@ export function EditorProviders({
|
|||
ProjectSettingsProvider,
|
||||
SplitTestProvider,
|
||||
UserProvider,
|
||||
UserSettingsProvider,
|
||||
...providers,
|
||||
}
|
||||
|
||||
|
@ -133,25 +154,27 @@ export function EditorProviders({
|
|||
<Providers.SplitTestProvider>
|
||||
<Providers.IdeAngularProvider ide={window._ide}>
|
||||
<Providers.UserProvider>
|
||||
<Providers.ProjectProvider>
|
||||
<Providers.FileTreeDataProvider>
|
||||
<Providers.FileTreePathProvider>
|
||||
<Providers.DetachProvider>
|
||||
<Providers.EditorProvider>
|
||||
<Providers.ProjectSettingsProvider>
|
||||
<Providers.LayoutProvider>
|
||||
<Providers.LocalCompileProvider>
|
||||
<Providers.DetachCompileProvider>
|
||||
{children}
|
||||
</Providers.DetachCompileProvider>
|
||||
</Providers.LocalCompileProvider>
|
||||
</Providers.LayoutProvider>
|
||||
</Providers.ProjectSettingsProvider>
|
||||
</Providers.EditorProvider>
|
||||
</Providers.DetachProvider>
|
||||
</Providers.FileTreePathProvider>
|
||||
</Providers.FileTreeDataProvider>
|
||||
</Providers.ProjectProvider>
|
||||
<Providers.UserSettingsProvider>
|
||||
<Providers.ProjectProvider>
|
||||
<Providers.FileTreeDataProvider>
|
||||
<Providers.FileTreePathProvider>
|
||||
<Providers.DetachProvider>
|
||||
<Providers.EditorProvider>
|
||||
<Providers.ProjectSettingsProvider>
|
||||
<Providers.LayoutProvider>
|
||||
<Providers.LocalCompileProvider>
|
||||
<Providers.DetachCompileProvider>
|
||||
{children}
|
||||
</Providers.DetachCompileProvider>
|
||||
</Providers.LocalCompileProvider>
|
||||
</Providers.LayoutProvider>
|
||||
</Providers.ProjectSettingsProvider>
|
||||
</Providers.EditorProvider>
|
||||
</Providers.DetachProvider>
|
||||
</Providers.FileTreePathProvider>
|
||||
</Providers.FileTreeDataProvider>
|
||||
</Providers.ProjectProvider>
|
||||
</Providers.UserSettingsProvider>
|
||||
</Providers.UserProvider>
|
||||
</Providers.IdeAngularProvider>
|
||||
</Providers.SplitTestProvider>
|
||||
|
|
19
services/web/types/user-settings.ts
Normal file
19
services/web/types/user-settings.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Keybindings, PdfViewer } from './project-settings'
|
||||
import {
|
||||
FontFamily,
|
||||
LineHeight,
|
||||
OverallTheme,
|
||||
} from '@/features/source-editor/extensions/theme'
|
||||
|
||||
export type UserSettings = {
|
||||
pdfViewer: PdfViewer
|
||||
autoComplete: boolean
|
||||
autoPairDelimiters: boolean
|
||||
syntaxValidation: boolean
|
||||
editorTheme: string
|
||||
overallTheme: OverallTheme
|
||||
mode: Keybindings
|
||||
fontSize: number
|
||||
fontFamily: FontFamily
|
||||
lineHeight: LineHeight
|
||||
}
|
|
@ -3,7 +3,7 @@ import { OAuthProviders } from './oauth-providers'
|
|||
import { OverallThemeMeta } from './project-settings'
|
||||
import { User } from './user'
|
||||
import 'recurly__recurly-js'
|
||||
import { UserSettings } from '@/features/editor-left-menu/utils/api'
|
||||
import { UserSettings } from './user-settings'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
Loading…
Reference in a new issue