Merge pull request #26583 from overleaf/td-editor-scope-values-to-context

Move scope values starting with `editor.` to contexts

GitOrigin-RevId: 7ca349ceff002228cf4e931c644c8c386eb6c597
This commit is contained in:
Tim Down
2025-07-09 08:05:15 +00:00
committed by Copybot
parent b2fb70c2b6
commit 132ccbc4cc
40 changed files with 593 additions and 292 deletions
@@ -9,8 +9,6 @@ import {
useState,
} from 'react'
import { sendMB } from '@/infrastructure/event-tracking'
import useScopeValue from '@/shared/hooks/use-scope-value'
import { useIdeContext } from '@/shared/context/ide-context'
import { OpenDocuments } from '@/features/ide-react/editor/open-documents'
import EditorWatchdogManager from '@/features/ide-react/connection/editor-watchdog-manager'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
@@ -37,6 +35,7 @@ import { Update } from '@/features/history/services/types/update'
import { useDebugDiffTracker } from '../hooks/use-debug-diff-tracker'
import { convertFileRefToBinaryFile } from '@/features/ide-react/util/file-view'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
export interface GotoOffsetOptions {
gotoOffset: number
@@ -52,7 +51,6 @@ interface OpenDocOptions
export type EditorManager = {
getEditorType: () => EditorType | null
showSymbolPalette: boolean
getCurrentDocValue: () => string | null
getCurrentDocumentId: () => DocId | null
setIgnoringExternalUpdates: (value: boolean) => void
@@ -62,12 +60,7 @@ export type EditorManager = {
openFileWithId: (fileId: string) => void
openInitialDoc: (docId: string) => void
isLoading: boolean
trackChanges: boolean
jumpToLine: (options: GotoLineOptions) => void
wantTrackChanges: boolean
setWantTrackChanges: React.Dispatch<
React.SetStateAction<EditorManager['wantTrackChanges']>
>
debugTimers: React.MutableRefObject<Record<string, number>>
}
@@ -87,7 +80,6 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { t } = useTranslation()
const { scopeStore } = useIdeContext()
const { reportError, eventEmitter, projectId, setOutOfSync } =
useIdeReactContext()
const { socket, closeConnection, connectionState } = useConnectionContext()
@@ -95,11 +87,15 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
const { showGenericMessageModal, genericModalVisible, showOutOfSyncModal } =
useModalsContext()
const { id: userId } = useUserContext()
const [showSymbolPalette, setShowSymbolPalette] = useScopeValue<boolean>(
'editor.showSymbolPalette'
)
const [showVisual] = useScopeValue<boolean>('editor.showVisual')
const {
showVisual,
opening,
setOpening,
errorState,
setErrorState,
setTrackChanges,
wantTrackChanges,
} = useEditorPropertiesContext()
const {
currentDocumentId,
setCurrentDocumentId,
@@ -107,15 +103,6 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
currentDocument,
setCurrentDocument,
} = useEditorOpenDocContext()
const [opening, setOpening] = useScopeValue<boolean>('editor.opening')
const [errorState, setIsInErrorState] =
useScopeValue<boolean>('editor.error_state')
const [trackChanges, setTrackChanges] = useScopeValue<boolean>(
'editor.trackChanges'
)
const [wantTrackChanges, setWantTrackChanges] = useScopeValue<boolean>(
'editor.wantTrackChanges'
)
const wantTrackChangesRef = useRef(wantTrackChanges)
useEffect(() => {
@@ -191,22 +178,6 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
const editorOpenDocEpochRef = useRef(0)
// TODO: This looks dodgy because it wraps a state setter and is itself
// stored in React state in the scope store. The problem is that it needs to
// be exposed via the scope store because some components access it that way;
// it would be better to simply access it from a context, but the current
// implementation in EditorManager interacts with Angular scope to update
// the layout. Once Angular is gone, this can become a context method.
useEffect(() => {
scopeStore.set('editor.toggleSymbolPalette', () => {
setShowSymbolPalette(show => {
const newValue = !show
sendMB(newValue ? 'symbol-palette-show' : 'symbol-palette-hide')
return newValue
})
})
}, [scopeStore, setShowSymbolPalette])
const getEditorType = useCallback((): EditorType | null => {
if (!currentDocument) {
return null
@@ -587,7 +558,7 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
reportError(error, meta)
// Tell the user about the error state.
setIsInErrorState(true)
setErrorState(true)
// Ensure that the editor is locked
setOutOfSync(true)
// Display the "out of sync" modal
@@ -614,7 +585,7 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
eventEmitter,
openDoc,
reportError,
setIsInErrorState,
setErrorState,
showGenericMessageModal,
showOutOfSyncModal,
setOutOfSync,
@@ -661,25 +632,20 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
const value: EditorManager = useMemo(
() => ({
getEditorType,
showSymbolPalette,
getCurrentDocValue,
getCurrentDocumentId,
setIgnoringExternalUpdates,
openDocWithId,
openDoc,
openDocs,
trackChanges,
isLoading,
openFileWithId,
openInitialDoc,
jumpToLine,
wantTrackChanges,
setWantTrackChanges,
debugTimers,
}),
[
getEditorType,
showSymbolPalette,
getCurrentDocValue,
getCurrentDocumentId,
setIgnoringExternalUpdates,
@@ -688,11 +654,8 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
openDocs,
openFileWithId,
openInitialDoc,
trackChanges,
isLoading,
jumpToLine,
wantTrackChanges,
setWantTrackChanges,
debugTimers,
]
)
@@ -0,0 +1,114 @@
import {
createContext,
Dispatch,
FC,
PropsWithChildren,
SetStateAction,
useCallback,
useContext,
useState,
} from 'react'
import customLocalStorage from '@/infrastructure/local-storage'
import usePersistedState from '@/shared/hooks/use-persisted-state'
import getMeta from '@/utils/meta'
import { useUnstableStoreSync } from '@/shared/hooks/use-unstable-store-sync'
import { sendMB } from '@/infrastructure/event-tracking'
// Context value type
export type EditorPropertiesContextValue = {
showVisual: boolean
setShowVisual: Dispatch<SetStateAction<boolean>>
showSymbolPalette: boolean
setShowSymbolPalette: Dispatch<SetStateAction<boolean>>
toggleSymbolPalette: () => void
opening: boolean
setOpening: Dispatch<SetStateAction<boolean>>
trackChanges: boolean
setTrackChanges: Dispatch<SetStateAction<boolean>>
wantTrackChanges: boolean
setWantTrackChanges: Dispatch<SetStateAction<boolean>>
errorState: boolean
setErrorState: Dispatch<SetStateAction<boolean>>
}
export const EditorPropertiesContext = createContext<
EditorPropertiesContextValue | undefined
>(undefined)
function showVisualFallbackValue() {
const projectId = getMeta('ol-project_id')
const editorModeKey = `editor.mode.${projectId}`
const editorModeVal = customLocalStorage.getItem(editorModeKey)
if (editorModeVal) {
// clean up the old key
customLocalStorage.removeItem(editorModeKey)
}
return editorModeVal === 'rich-text'
}
export const EditorPropertiesProvider: FC<PropsWithChildren> = ({
children,
}) => {
const [showVisual, setShowVisual] = usePersistedState(
`editor.lastUsedMode`,
showVisualFallbackValue(),
{
converter: {
toPersisted: showVisual => (showVisual ? 'visual' : 'code'),
fromPersisted: mode => mode === 'visual',
},
}
)
// Sync the showVisual state with the exposed store
useUnstableStoreSync('editor.showVisual', showVisual)
const [showSymbolPalette, setShowSymbolPalette] = useState(false)
const toggleSymbolPalette = useCallback(() => {
setShowSymbolPalette(show => {
const newValue = !show
sendMB(newValue ? 'symbol-palette-show' : 'symbol-palette-hide')
return newValue
})
}, [setShowSymbolPalette])
const [opening, setOpening] = useState(true)
const [trackChanges, setTrackChanges] = useState(false)
const [wantTrackChanges, setWantTrackChanges] = useState(false)
const [errorState, setErrorState] = useState(false)
const value = {
showVisual,
setShowVisual,
showSymbolPalette,
setShowSymbolPalette,
toggleSymbolPalette,
opening,
setOpening,
trackChanges,
setTrackChanges,
wantTrackChanges,
setWantTrackChanges,
errorState,
setErrorState,
}
return (
<EditorPropertiesContext.Provider value={value}>
{children}
</EditorPropertiesContext.Provider>
)
}
export const useEditorPropertiesContext = (): EditorPropertiesContextValue => {
const context = useContext(EditorPropertiesContext)
if (!context) {
throw new Error(
'useEditorPropertiesContext is only available inside EditorPropertiesContext.Provider'
)
}
return context
}
@@ -15,7 +15,6 @@ import {
} from '@/features/ide-react/create-ide-event-emitter'
import { JoinProjectPayload } from '@/features/ide-react/connection/join-project-payload'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import { populateEditorScope } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter'
import { postJSON } from '@/infrastructure/fetch-json'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
import getMeta from '@/utils/meta'
@@ -53,7 +52,7 @@ function populatePdfScope(store: ReactScopeValueStore) {
store.allowNonExistentPath('pdf', true)
}
export function createReactScopeValueStore(projectId: string) {
export function createReactScopeValueStore() {
const scopeStore = new ReactScopeValueStore()
// Populate the scope value store with default values that will be used by
@@ -62,7 +61,6 @@ export function createReactScopeValueStore(projectId: string) {
// initialization code together with the context and would only populate
// necessary values in the store, but this is simpler for now
populateIdeReactScope(scopeStore)
populateEditorScope(scopeStore, projectId)
populateProjectScope(scopeStore)
populatePdfScope(scopeStore)
@@ -73,7 +71,7 @@ export function createReactScopeValueStore(projectId: string) {
export const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
const projectId = getMeta('ol-project_id')
const [scopeStore] = useState(() => createReactScopeValueStore(projectId))
const [scopeStore] = useState(() => createReactScopeValueStore())
const [eventEmitter] = useState(createIdeEventEmitter)
const [permissionsLevel, setPermissionsLevel] =
useState<PermissionsLevel>('readOnly')
@@ -5,6 +5,7 @@ import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
import { DetachProvider } from '@/shared/context/detach-context'
import { EditorManagerProvider } from '@/features/ide-react/context/editor-manager-context'
import { EditorOpenDocProvider } from '@/features/ide-react/context/editor-open-doc-context'
import { EditorPropertiesProvider } from '@/features/ide-react/context/editor-properties-context'
import { EditorProvider } from '@/shared/context/editor-context'
import { EditorViewProvider } from '@/features/ide-react/context/editor-view-context'
import { FileTreeDataProvider } from '@/shared/context/file-tree-data-context'
@@ -42,6 +43,7 @@ export const ReactContextRoot: FC<
DetachProvider,
EditorManagerProvider,
EditorOpenDocProvider,
EditorPropertiesProvider,
EditorProvider,
EditorViewProvider,
FileTreeDataProvider,
@@ -79,49 +81,51 @@ export const ReactContextRoot: FC<
<Providers.ProjectProvider>
<Providers.SnapshotProvider>
<Providers.DetachProvider>
<Providers.EditorViewProvider>
<Providers.EditorOpenDocProvider>
<Providers.EditorProvider>
<Providers.FileTreeDataProvider>
<Providers.FileTreePathProvider>
<Providers.ReferencesProvider>
<Providers.UserFeaturesProvider>
<Providers.PermissionsProvider>
<Providers.RailProvider>
<Providers.LayoutProvider>
<Providers.ProjectSettingsProvider>
<Providers.EditorManagerProvider>
<Providers.LocalCompileProvider>
<Providers.DetachCompileProvider>
<Providers.ChatProvider>
<Providers.FileTreeOpenProvider>
<Providers.OnlineUsersProvider>
<Providers.MetadataProvider>
<Providers.OutlineProvider>
<Providers.IdeRedesignSwitcherProvider>
<Providers.CommandRegistryProvider>
{children}
</Providers.CommandRegistryProvider>
</Providers.IdeRedesignSwitcherProvider>
</Providers.OutlineProvider>
</Providers.MetadataProvider>
</Providers.OnlineUsersProvider>
</Providers.FileTreeOpenProvider>
</Providers.ChatProvider>
</Providers.DetachCompileProvider>
</Providers.LocalCompileProvider>
</Providers.EditorManagerProvider>
</Providers.ProjectSettingsProvider>
</Providers.LayoutProvider>
</Providers.RailProvider>
</Providers.PermissionsProvider>
</Providers.UserFeaturesProvider>
</Providers.ReferencesProvider>
</Providers.FileTreePathProvider>
</Providers.FileTreeDataProvider>
</Providers.EditorProvider>
</Providers.EditorOpenDocProvider>
</Providers.EditorViewProvider>
<Providers.EditorPropertiesProvider>
<Providers.EditorViewProvider>
<Providers.EditorOpenDocProvider>
<Providers.EditorProvider>
<Providers.FileTreeDataProvider>
<Providers.FileTreePathProvider>
<Providers.ReferencesProvider>
<Providers.UserFeaturesProvider>
<Providers.PermissionsProvider>
<Providers.RailProvider>
<Providers.LayoutProvider>
<Providers.ProjectSettingsProvider>
<Providers.EditorManagerProvider>
<Providers.LocalCompileProvider>
<Providers.DetachCompileProvider>
<Providers.ChatProvider>
<Providers.FileTreeOpenProvider>
<Providers.OnlineUsersProvider>
<Providers.MetadataProvider>
<Providers.OutlineProvider>
<Providers.IdeRedesignSwitcherProvider>
<Providers.CommandRegistryProvider>
{children}
</Providers.CommandRegistryProvider>
</Providers.IdeRedesignSwitcherProvider>
</Providers.OutlineProvider>
</Providers.MetadataProvider>
</Providers.OnlineUsersProvider>
</Providers.FileTreeOpenProvider>
</Providers.ChatProvider>
</Providers.DetachCompileProvider>
</Providers.LocalCompileProvider>
</Providers.EditorManagerProvider>
</Providers.ProjectSettingsProvider>
</Providers.LayoutProvider>
</Providers.RailProvider>
</Providers.PermissionsProvider>
</Providers.UserFeaturesProvider>
</Providers.ReferencesProvider>
</Providers.FileTreePathProvider>
</Providers.FileTreeDataProvider>
</Providers.EditorProvider>
</Providers.EditorOpenDocProvider>
</Providers.EditorViewProvider>
</Providers.EditorPropertiesProvider>
</Providers.DetachProvider>
</Providers.SnapshotProvider>
</Providers.ProjectProvider>