Merge pull request #26326 from overleaf/td-clean-up-scope-store

Create separate window.overleaf.unstable.store based on React context values

GitOrigin-RevId: 68f070a6fc5e2965a82720024d91b16fa622b28b
This commit is contained in:
Tim Down
2025-07-02 08:53:15 +01:00
committed by Copybot
parent 63c515cf10
commit 409d5f7b3e
34 changed files with 621 additions and 251 deletions

View File

@@ -3,6 +3,7 @@ import React, { FC, lazy, Suspense } from 'react'
import useScopeValue from '@/shared/hooks/use-scope-value'
import SourceEditor from '@/features/source-editor/components/source-editor'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { EditorScopeValue } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter'
import classNames from 'classnames'
import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
@@ -17,7 +18,8 @@ const SymbolPalettePane = lazy(
export const EditorPane: FC = () => {
const [editor] = useScopeValue<EditorScopeValue>('editor')
const { selectedEntityCount, openEntity } = useFileTreeOpenContext()
const { currentDocumentId, isLoading } = useEditorManagerContext()
const { isLoading } = useEditorManagerContext()
const { currentDocumentId } = useEditorOpenDocContext()
if (!currentDocumentId) {
return null

View File

@@ -37,6 +37,7 @@ import { Update } from '@/features/history/services/types/update'
import { useDebugDiffTracker } from '../hooks/use-debug-diff-tracker'
import { useEditorContext } from '@/shared/context/editor-context'
import { convertFileRefToBinaryFile } from '@/features/ide-react/util/file-view'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
export interface GotoOffsetOptions {
gotoOffset: number
@@ -53,8 +54,6 @@ interface OpenDocOptions
export type EditorManager = {
getEditorType: () => EditorType | null
showSymbolPalette: boolean
currentDocument: DocumentContainer | null
currentDocumentId: DocId | null
getCurrentDocValue: () => string | null
getCurrentDocumentId: () => DocId | null
setIgnoringExternalUpdates: (value: boolean) => void
@@ -63,8 +62,6 @@ export type EditorManager = {
openDocs: OpenDocuments
openFileWithId: (fileId: string) => void
openInitialDoc: (docId: string) => void
openDocName: string | null
setOpenDocName: (openDocName: string) => void
isLoading: boolean
trackChanges: boolean
jumpToLine: (options: GotoLineOptions) => void
@@ -104,14 +101,13 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
'editor.showSymbolPalette'
)
const [showVisual] = useScopeValue<boolean>('editor.showVisual')
const [currentDocument, setCurrentDocument] =
useScopeValue<DocumentContainer | null>('editor.sharejs_doc')
const [currentDocumentId, setCurrentDocumentId] = useScopeValue<DocId | null>(
'editor.open_doc_id'
)
const [openDocName, setOpenDocName] = useScopeValue<string | null>(
'editor.open_doc_name'
)
const {
currentDocumentId,
setCurrentDocumentId,
setOpenDocName,
currentDocument,
setCurrentDocument,
} = useEditorOpenDocContext()
const [opening, setOpening] = useScopeValue<boolean>('editor.opening')
const [errorState, setIsInErrorState] =
useScopeValue<boolean>('editor.error_state')
@@ -667,16 +663,12 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
() => ({
getEditorType,
showSymbolPalette,
currentDocument,
currentDocumentId,
getCurrentDocValue,
getCurrentDocumentId,
setIgnoringExternalUpdates,
openDocWithId,
openDoc,
openDocs,
openDocName,
setOpenDocName,
trackChanges,
isLoading,
openFileWithId,
@@ -689,8 +681,6 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
[
getEditorType,
showSymbolPalette,
currentDocument,
currentDocumentId,
getCurrentDocValue,
getCurrentDocumentId,
setIgnoringExternalUpdates,
@@ -699,8 +689,6 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
openDocs,
openFileWithId,
openInitialDoc,
openDocName,
setOpenDocName,
trackChanges,
isLoading,
jumpToLine,

View File

@@ -0,0 +1,63 @@
import {
createContext,
Dispatch,
FC,
PropsWithChildren,
SetStateAction,
useContext,
useState,
} from 'react'
import { DocId } from '../../../../../types/project-settings'
import useExposedState from '@/shared/hooks/use-exposed-state'
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
type EditorOpenDocContextValue = {
currentDocumentId: DocId | null
setCurrentDocumentId: Dispatch<SetStateAction<DocId | null>>
openDocName: string | null
setOpenDocName: Dispatch<SetStateAction<string | null>>
currentDocument: DocumentContainer | null
setCurrentDocument: Dispatch<SetStateAction<DocumentContainer | null>>
}
export const EditorOpenDocContext = createContext<
EditorOpenDocContextValue | undefined
>(undefined)
export const EditorOpenDocProvider: FC<PropsWithChildren> = ({ children }) => {
const [currentDocumentId, setCurrentDocumentId] =
useExposedState<DocId | null>(null, 'editor.open_doc_id')
const [openDocName, setOpenDocName] = useExposedState<string | null>(
null,
'editor.open_doc_name'
)
const [currentDocument, setCurrentDocument] =
useState<DocumentContainer | null>(null)
const value = {
currentDocumentId,
setCurrentDocumentId,
openDocName,
setOpenDocName,
currentDocument,
setCurrentDocument,
}
return (
<EditorOpenDocContext.Provider value={value}>
{children}
</EditorOpenDocContext.Provider>
)
}
export const useEditorOpenDocContext = (): EditorOpenDocContextValue => {
const context = useContext(EditorOpenDocContext)
if (!context) {
throw new Error(
'useEditorOpenDocContext is only available inside EditorOpenDocContext.Provider'
)
}
return context
}

View File

@@ -0,0 +1,49 @@
import {
createContext,
Dispatch,
FC,
PropsWithChildren,
SetStateAction,
useContext,
} from 'react'
import { EditorView } from '@codemirror/view'
import useExposedState from '@/shared/hooks/use-exposed-state'
export type EditorContextValue = {
view: EditorView | null
setView: Dispatch<SetStateAction<EditorView | null>>
}
// This provides access to the CodeMirror EditorView instance outside the editor
// component itself, including external extensions (in particular, Writefull)
export const EditorViewContext = createContext<EditorContextValue | undefined>(
undefined
)
export const EditorViewProvider: FC<PropsWithChildren> = ({ children }) => {
const [view, setView] = useExposedState<EditorView | null>(
null,
'editor.view'
)
const value = {
view,
setView,
}
return (
<EditorViewContext.Provider value={value}>
{children}
</EditorViewContext.Provider>
)
}
export const useEditorViewContext = (): EditorContextValue => {
const context = useContext(EditorViewContext)
if (!context) {
throw new Error(
'useEditorViewContext is only available inside EditorViewProvider'
)
}
return context
}

View File

@@ -11,6 +11,7 @@ import {
import { useProjectContext } from '@/shared/context/project-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import {
FileTreeDocumentFindResult,
FileTreeFileRefFindResult,
@@ -40,8 +41,8 @@ export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
}) => {
const { rootDocId, owner } = useProjectContext()
const { eventEmitter, projectJoined } = useIdeReactContext()
const { openDocWithId, currentDocumentId, openInitialDoc } =
useEditorManagerContext()
const { openDocWithId, openInitialDoc } = useEditorManagerContext()
const { currentDocumentId } = useEditorOpenDocContext()
const { setOpenFile } = useLayoutContext()
const [openEntity, setOpenEntity] = useState<
FileTreeDocumentFindResult | FileTreeFileRefFindResult | null

View File

@@ -81,6 +81,13 @@ export const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
const [scopeEventEmitter] = useState(
() => new ReactScopeEventEmitter(eventEmitter)
)
const [unstableStore] = useState(() => {
const store = new ReactScopeValueStore()
// Add dummy editor.ready key for Writefull, that relies on this calling
// back once after watching it
store.set('editor.ready', undefined)
return store
})
const [startedFreeTrial, setStartedFreeTrial] = useState(false)
const release = getMeta('ol-ExposedSettings')?.sentryRelease ?? null
@@ -179,6 +186,7 @@ export const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
ide={ide}
scopeStore={scopeStore}
scopeEventEmitter={scopeEventEmitter}
unstableStore={unstableStore}
>
{children}
</IdeProvider>

View File

@@ -10,7 +10,7 @@ import {
} from 'react'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { getJSON, postJSON } from '@/infrastructure/fetch-json'
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
import { useEditorContext } from '@/shared/context/editor-context'
@@ -54,7 +54,7 @@ export const MetadataProvider: FC<React.PropsWithChildren> = ({ children }) => {
const { onlineUsersCount } = useOnlineUsersContext()
const { permissionsLevel } = useEditorContext()
const permissions = usePermissionsContext()
const { currentDocument } = useEditorManagerContext()
const { currentDocument } = useEditorOpenDocContext()
const { showGenericMessageModal } = useModalsContext()
const [documents, setDocuments] = useState<DocumentsMetadata>({})

View File

@@ -18,7 +18,7 @@ import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
import { debugConsole } from '@/utils/debugging'
import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
import { getHueForUserId } from '@/shared/utils/colors'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
export type OnlineUser = {
id: string
@@ -70,7 +70,7 @@ export const OnlineUsersProvider: FC<React.PropsWithChildren> = ({
}) => {
const { eventEmitter } = useIdeReactContext()
const { socket } = useConnectionContext()
const { currentDocumentId } = useEditorManagerContext()
const { currentDocumentId } = useEditorOpenDocContext()
const { fileTreeData } = useFileTreeData()
const [onlineUsers, setOnlineUsers] = useState<Record<string, OnlineUser>>({})

View File

@@ -14,7 +14,7 @@ import * as eventTracking from '@/infrastructure/event-tracking'
import { isValidTeXFile } from '@/main/is-valid-tex-file'
import localStorage from '@/infrastructure/local-storage'
import { useProjectContext } from '@/shared/context/project-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
export type PartialFlatOutline = {
level: number
@@ -118,7 +118,7 @@ export const OutlineProvider: FC<React.PropsWithChildren> = ({ children }) => {
[flatOutline, currentlyHighlightedLine]
)
const { openDocName } = useEditorManagerContext()
const { openDocName } = useEditorOpenDocContext()
const isTexFile = useMemo(
() => (openDocName ? isValidTeXFile(openDocName) : false),
[openDocName]

View File

@@ -4,7 +4,9 @@ import { ConnectionProvider } from './connection-context'
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 { 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'
import { FileTreeOpenProvider } from '@/features/ide-react/context/file-tree-open-context'
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
@@ -39,7 +41,9 @@ export const ReactContextRoot: FC<
DetachCompileProvider,
DetachProvider,
EditorManagerProvider,
EditorOpenDocProvider,
EditorProvider,
EditorViewProvider,
FileTreeDataProvider,
FileTreeOpenProvider,
FileTreePathProvider,
@@ -74,47 +78,51 @@ export const ReactContextRoot: FC<
<Providers.UserSettingsProvider>
<Providers.ProjectProvider>
<Providers.SnapshotProvider>
<Providers.FileTreeDataProvider>
<Providers.FileTreePathProvider>
<Providers.ReferencesProvider>
<Providers.DetachProvider>
<Providers.EditorProvider>
<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.EditorProvider>
</Providers.DetachProvider>
</Providers.ReferencesProvider>
</Providers.FileTreePathProvider>
</Providers.FileTreeDataProvider>
<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.DetachProvider>
</Providers.SnapshotProvider>
</Providers.ProjectProvider>
</Providers.UserSettingsProvider>

View File

@@ -6,8 +6,6 @@ export type EditorScopeValue = {
showSymbolPalette: false
toggleSymbolPalette: () => void
sharejs_doc: DocumentContainer | null
open_doc_id: string | null
open_doc_name: string | null
opening: boolean
trackChanges: boolean
wantTrackChanges: boolean
@@ -25,8 +23,6 @@ export function populateEditorScope(
showSymbolPalette: false,
toggleSymbolPalette: () => {},
sharejs_doc: null,
open_doc_id: null,
open_doc_name: null,
opening: true,
trackChanges: false,
wantTrackChanges: false,

View File

@@ -1,5 +1,5 @@
import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { EditorScopeValue } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter'
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
import useScopeValue from '@/shared/hooks/use-scope-value'
@@ -14,16 +14,16 @@ import SymbolPalettePane from '@/features/ide-react/components/editor/symbol-pal
export const Editor = () => {
const [editor] = useScopeValue<EditorScopeValue>('editor')
const { selectedEntityCount, openEntity } = useFileTreeOpenContext()
const { currentDocumentId } = useEditorManagerContext()
const { currentDocumentId, currentDocument } = useEditorOpenDocContext()
if (!currentDocumentId) {
return null
}
const isLoading = Boolean(
(!editor.sharejs_doc || editor.opening) &&
(!currentDocument || editor.opening) &&
!editor.error_state &&
editor.open_doc_id
currentDocumentId
)
return (

View File

@@ -12,7 +12,7 @@ export const usePdfPreviewContext = () => {
const context = useContext(PdfPreviewContext)
if (!context) {
throw new Error(
'usePdfPreviewContext is only avalable inside PdfPreviewProvider'
'usePdfPreviewContext is only available inside PdfPreviewProvider'
)
}
return context

View File

@@ -13,6 +13,7 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
import { debugConsole } from '@/utils/debugging'
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import useEventListener from '@/shared/hooks/use-event-listener'
import { CursorPosition } from '@/features/ide-react/types/cursor-position'
import { isValidTeXFile } from '@/main/is-valid-tex-file'
@@ -34,8 +35,8 @@ export default function useSynctex(): {
const { selectedEntities } = useFileTreeData()
const { findEntityByPath, dirname, pathInFolder } = useFileTreePathContext()
const { getCurrentDocumentId, openDocWithId, openDocName } =
useEditorManagerContext()
const { openDocName } = useEditorOpenDocContext()
const { getCurrentDocumentId, openDocWithId } = useEditorManagerContext()
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(
() => {

View File

@@ -21,7 +21,7 @@ import { useIdeReactContext } from '@/features/ide-react/context/ide-react-conte
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
import { throttle } from 'lodash'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
export type Ranges = {
docId: string
@@ -81,7 +81,7 @@ const RangesActionsContext = createContext<RangesActions | undefined>(undefined)
export const RangesProvider: FC<React.PropsWithChildren> = ({ children }) => {
const view = useCodeMirrorViewContext()
const { projectId } = useIdeReactContext()
const { currentDocument } = useEditorManagerContext()
const { currentDocument } = useEditorOpenDocContext()
const { socket } = useConnectionContext()
const [ranges, setRanges] = useState<Ranges | undefined>(() =>
buildRanges(currentDocument)

View File

@@ -20,7 +20,7 @@ import { UserId } from '../../../../../types/user'
import { deleteJSON, getJSON, postJSON } from '@/infrastructure/fetch-json'
import RangesTracker from '@overleaf/ranges-tracker'
import { CommentOperation } from '../../../../../types/change'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { useEditorContext } from '@/shared/context/editor-context'
import { debugConsole } from '@/utils/debugging'
import { captureException } from '@/infrastructure/error-reporter'
@@ -50,7 +50,7 @@ const ThreadsActionsContext = createContext<ThreadsActions | undefined>(
export const ThreadsProvider: FC<React.PropsWithChildren> = ({ children }) => {
const { _id: projectId } = useProjectContext()
const { currentDocument } = useEditorManagerContext()
const { currentDocument } = useEditorOpenDocContext()
const { isRestrictedTokenMember } = useEditorContext()
// const [error, setError] = useState<Error>()

View File

@@ -1,12 +1,12 @@
import { memo, useCallback, useEffect } from 'react'
import { useCodeMirrorViewContext } from './codemirror-context'
import useCodeMirrorScope from '../hooks/use-codemirror-scope'
import useScopeValueSetterOnly from '@/shared/hooks/use-scope-value-setter-only'
import { useEditorViewContext } from '@/features/ide-react/context/editor-view-context'
function CodeMirrorView() {
const view = useCodeMirrorViewContext()
const [, setView] = useScopeValueSetterOnly('editor.view')
const { setView } = useEditorViewContext()
// append the editor view dom to the container node when mounted
const containerRef = useCallback(
@@ -25,7 +25,8 @@ function CodeMirrorView() {
}
}, [view])
// add the editor view to the scope value store, so it can be accessed by external extensions
// Add the CodeMirror view to the editor view context so that it can be
// accessed outside the editor component
useEffect(() => {
setView(view)
}, [setView, view])

View File

@@ -4,12 +4,12 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { sendMB } from '../../../infrastructure/event-tracking'
import { isValidTeXFile } from '../../../main/is-valid-tex-file'
import { useTranslation } from 'react-i18next'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
function EditorSwitch() {
const { t } = useTranslation()
const [visual, setVisual] = useScopeValue('editor.showVisual')
const { openDocName } = useEditorManagerContext()
const { openDocName } = useEditorOpenDocContext()
const richTextAvailable = openDocName ? isValidTeXFile(openDocName) : false

View File

@@ -57,6 +57,7 @@ import {
} from '@/features/ide-react/context/editor-manager-context'
import { GotoLineOptions } from '@/features/ide-react/types/goto-line-options'
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@@ -68,8 +69,8 @@ function useCodeMirrorScope(view: EditorView) {
const { logEntryAnnotations, editedSinceCompileStarted, compiling } =
useCompileContext()
const { currentDocument, openDocName, trackChanges } =
useEditorManagerContext()
const { openDocName, currentDocument } = useEditorOpenDocContext()
const { trackChanges } = useEditorManagerContext()
const metadata = useMetadataContext()
const { id: userId } = useUserContext()

View File

@@ -6,6 +6,7 @@ import { useProjectContext } from '@/shared/context/project-context'
import useAbortController from '@/shared/hooks/use-abort-controller'
import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import { debugConsole } from '@/utils/debugging'
import { signalWithTimeout } from '@/utils/abort-signal'
@@ -20,7 +21,8 @@ export const WordCountClient: FC = () => {
const [data, setData] = useState<WordCountData | null>(null)
const { projectSnapshot, rootDocId } = useProjectContext()
const { spellCheckLanguage } = useProjectSettingsContext()
const { openDocs, currentDocument } = useEditorManagerContext()
const { openDocs } = useEditorManagerContext()
const { currentDocument } = useEditorOpenDocContext()
const { pathInFolder } = useFileTreePathContext()
const { signal } = useAbortController()

View File

@@ -18,7 +18,7 @@ import {
import { countFiles } from '../../features/file-tree/util/count-in-tree'
import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect'
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
import useScopeValueSetterOnly from '@/shared/hooks/use-scope-value-setter-only'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { Folder } from '../../../../types/folder'
import { Project } from '../../../../types/project'
import { MainDocument } from '../../../../types/project-settings'
@@ -180,8 +180,7 @@ export const FileTreeDataProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [project] = useScopeValue<Project>('project')
const [currentDocumentId] = useScopeValue('editor.open_doc_id')
const [, setOpenDocName] = useScopeValueSetterOnly('editor.open_doc_name')
const { currentDocumentId, setOpenDocName } = useEditorOpenDocContext()
const [permissionsLevel] = useScopeValue('permissionsLevel')
const { fileTreeFromHistory, snapshot, snapshotVersion } =
useSnapshotContext()

View File

@@ -10,6 +10,7 @@ export type Ide = {
type IdeContextValue = Ide & {
scopeStore: ScopeValueStore
scopeEventEmitter: ScopeEventEmitter
unstableStore: ScopeValueStore
}
export const IdeContext = createContext<IdeContextValue | undefined>(undefined)
@@ -19,14 +20,14 @@ export const IdeProvider: FC<
ide: Ide
scopeStore: ScopeValueStore
scopeEventEmitter: ScopeEventEmitter
unstableStore: ScopeValueStore
}>
> = ({ ide, scopeStore, scopeEventEmitter, children }) => {
> = ({ ide, scopeStore, scopeEventEmitter, unstableStore, children }) => {
/**
* Expose scopeStore via `window.overleaf.unstable.store`, so it can be accessed by external extensions.
* Expose unstableStore via `window.overleaf.unstable.store`, so it can be accessed by external extensions.
*
* These properties are expected to be available:
* - `editor.view`
* - `project.spellcheckLanguage`
* - `editor.open_doc_name`,
* - `editor.open_doc_id`,
* - `settings.theme`
@@ -40,18 +41,19 @@ export const IdeProvider: FC<
...window.overleaf,
unstable: {
...window.overleaf?.unstable,
store: scopeStore,
store: unstableStore,
},
}
}, [scopeStore])
}, [unstableStore])
const value = useMemo<IdeContextValue>(() => {
return {
...ide,
scopeStore,
scopeEventEmitter,
unstableStore,
}
}, [ide, scopeStore, scopeEventEmitter])
}, [ide, scopeStore, scopeEventEmitter, unstableStore])
return <IdeContext.Provider value={value}>{children}</IdeContext.Provider>
}

View File

@@ -39,6 +39,7 @@ import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { getJSON } from '@/infrastructure/fetch-json'
import { CompileResponseData } from '../../../../types/compile'
import {
@@ -127,7 +128,8 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { hasPremiumCompile, isProjectOwner } = useEditorContext()
const { openDocWithId, openDocs, currentDocument } = useEditorManagerContext()
const { openDocWithId, openDocs } = useEditorManagerContext()
const { currentDocument } = useEditorOpenDocContext()
const { role } = useDetachContext()
const {

View File

@@ -9,11 +9,11 @@ import {
useEffect,
} from 'react'
import { UserSettings, Keybindings } from '../../../../types/user-settings'
import { UserSettings } from '../../../../types/user-settings'
import getMeta from '@/utils/meta'
import useScopeValue from '@/shared/hooks/use-scope-value'
import { userStyles } from '../utils/styles'
import { canUseNewEditor } from '@/features/ide-redesign/utils/new-editor-utils'
import { useIdeContext } from '@/shared/context/ide-context'
const defaultSettings: UserSettings = {
pdfViewer: 'pdfjs',
@@ -39,15 +39,6 @@ type UserSettingsContextValue = {
>
}
type ScopeSettings = {
overallTheme: 'light' | 'dark'
keybindings: Keybindings
fontSize: number
fontFamily: string
lineHeight: number
isNewEditor: boolean
}
export const UserSettingsContext = createContext<
UserSettingsContextValue | undefined
>(undefined)
@@ -60,10 +51,10 @@ export const UserSettingsProvider: FC<React.PropsWithChildren> = ({
)
// update the global scope 'settings' value, for extensions
const [, setScopeSettings] = useScopeValue<ScopeSettings>('settings')
const { unstableStore } = useIdeContext()
useEffect(() => {
const { fontFamily, lineHeight } = userStyles(userSettings)
setScopeSettings({
unstableStore.set('settings', {
overallTheme: userSettings.overallTheme === 'light-' ? 'light' : 'dark',
keybindings: userSettings.mode === 'none' ? 'default' : userSettings.mode,
fontFamily,
@@ -71,7 +62,7 @@ export const UserSettingsProvider: FC<React.PropsWithChildren> = ({
fontSize: userSettings.fontSize,
isNewEditor: canUseNewEditor() && userSettings.enableNewEditor,
})
}, [setScopeSettings, userSettings])
}, [unstableStore, userSettings])
const value = useMemo<UserSettingsContextValue>(
() => ({

View File

@@ -0,0 +1,25 @@
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react'
import { useIdeContext } from '../context/ide-context'
/**
* Creates a state variable that is exposed via window.overleaf.unstable.store,
* which is used by Writefull (and only Writefull). Once Writefull is integrated
* into our codebase, it should be able to hook directly into our React
* contexts and we would then be able to remove this hook, replacing it with
* useState.
*/
export default function useExposedState<T = any>(
initialState: T | (() => T),
path: string
): [T, Dispatch<SetStateAction<T>>] {
const [value, setValue] = useState<T>(initialState)
const { unstableStore } = useIdeContext()
// Update the unstable store whenever the value changes
useEffect(() => {
unstableStore.set(path, value)
}, [unstableStore, path, value])
return [value, setValue]
}

View File

@@ -15,6 +15,7 @@ import {
createReactScopeValueStore,
} from '@/features/ide-react/context/ide-react-context'
import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
import { ConnectionContext } from '@/features/ide-react/context/connection-context'
import { Socket } from '@/features/ide-react/connection/types/socket'
@@ -65,6 +66,8 @@ const initialScope = {
},
editor: {
richText: false,
// FIXME: This is pretty useless because the editor relies on a much more fleshed-out document, so we rely on overriding it in individual stories
sharejs_doc: {
doc_id: 'test-doc',
getSnapshot: () => 'some doc content',
@@ -195,12 +198,13 @@ const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
scopeStore.set(key, value)
}
const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter())
const unstableStore = new ReactScopeValueStore()
window.overleaf = {
...window.overleaf,
unstable: {
...window.overleaf?.unstable,
store: scopeStore,
store: unstableStore,
},
}
@@ -208,6 +212,7 @@ const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
socket,
scopeStore,
scopeEventEmitter,
unstableStore,
}
})

View File

@@ -4,7 +4,7 @@ import { ruleIds } from '@/ide/human-readable-logs/HumanReadableLogsHints'
import { ScopeDecorator } from './decorators/scope'
import { useMeta } from './hooks/use-meta'
import { FC, ReactNode } from 'react'
import { useScope } from './hooks/use-scope'
import { EditorViewContext } from '@/features/ide-react/context/editor-view-context'
import { EditorView } from '@codemirror/view'
import { LogEntry } from '@/features/pdf-preview/util/types'
@@ -58,12 +58,30 @@ export default meta
type Story = StoryObj<typeof PdfLogEntry>
const MockEditorViewProvider: FC<React.PropsWithChildren> = ({ children }) => {
const value = {
view: new EditorView({
doc: '\\begin{document',
}),
setView: () => {},
}
return (
<EditorViewContext.Provider value={value}>
{children}
</EditorViewContext.Provider>
)
}
const Provider: FC<React.PropsWithChildren<{ children: ReactNode }>> = ({
children,
}) => {
useMeta({ 'ol-showAiErrorAssistant': true })
useScope({ 'editor.view': new EditorView({ doc: '\\begin{document' }) })
return <div className="logs-pane p-2">{children}</div>
return (
<MockEditorViewProvider>
<div className="logs-pane p-2">{children}</div>
</MockEditorViewProvider>
)
}
export const PdfLogEntryWithControls: Story = {

View File

@@ -2,9 +2,86 @@ import SourceEditor from '../../js/features/source-editor/components/source-edit
import { ScopeDecorator } from '../decorators/scope'
import { useScope } from '../hooks/use-scope'
import { useMeta } from '../hooks/use-meta'
import { FC } from 'react'
import React, { FC, useState } from 'react'
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import RangesTracker from '@overleaf/ranges-tracker'
import useExposedState from '@/shared/hooks/use-exposed-state'
import { EditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { DocId } from '../../../types/project-settings'
import { StoryObj } from '@storybook/react'
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
type Story = StoryObj<typeof SourceEditor>
const EditorOpenDocProvider: FC<
React.PropsWithChildren<{
initialOpenDocName: string | null
initialDocument: DocumentContainer
}>
> = ({ children, initialOpenDocName, initialDocument }) => {
const [currentDocumentId, setCurrentDocumentId] =
useExposedState<DocId | null>(null, 'editor.open_doc_id')
const [openDocName, setOpenDocName] = useExposedState<string | null>(
initialOpenDocName,
'editor.open_doc_name'
)
const [currentDocument, setCurrentDocument] =
useState<DocumentContainer | null>(initialDocument)
const value = {
currentDocumentId,
setCurrentDocumentId,
openDocName,
setOpenDocName,
currentDocument,
setCurrentDocument,
}
return (
<EditorOpenDocContext.Provider value={value}>
{children}
</EditorOpenDocContext.Provider>
)
}
const LatexEditorOpenDocProvider: FC<React.PropsWithChildren> = ({
children,
}) => (
<EditorOpenDocProvider
initialOpenDocName="example.tex"
initialDocument={
mockDoc(content.tex, changes.tex) as unknown as DocumentContainer
}
>
{children}
</EditorOpenDocProvider>
)
const MarkdownEditorOpenDocProvider: FC<React.PropsWithChildren> = ({
children,
}) => (
<EditorOpenDocProvider
initialOpenDocName="example.md"
initialDocument={
mockDoc(content.md, changes.md) as unknown as DocumentContainer
}
>
{children}
</EditorOpenDocProvider>
)
const BibtexEditorOpenDocProvider: FC<React.PropsWithChildren> = ({
children,
}) => (
<EditorOpenDocProvider
initialOpenDocName="example.bib"
initialDocument={
mockDoc(content.bib, changes.bib) as unknown as DocumentContainer
}
>
{children}
</EditorOpenDocProvider>
)
const FileTreePathProvider: FC<React.PropsWithChildren> = ({ children }) => (
<FileTreePathContext.Provider
@@ -29,11 +106,6 @@ export default {
title: 'Editor / Source Editor',
component: SourceEditor,
decorators: [
(Story: any) =>
ScopeDecorator(Story, {
mockCompileOnLoad: true,
providers: { FileTreePathProvider },
}),
(Story: any) => (
<div style={{ height: '90vh' }}>
<Story />
@@ -59,102 +131,144 @@ const permissions = {
write: true,
}
export const Latex = (args: any, { globals: { theme } }: any) => {
// FIXME: useScope has no effect
useScope({
editor: {
sharejs_doc: mockDoc(content.tex, changes.tex),
open_doc_name: 'example.tex',
},
rootFolder: {
name: 'rootFolder',
id: 'root-folder-id',
type: 'folder',
children: [
{
name: 'example.tex.tex',
id: 'example-doc-id',
type: 'doc',
export const Latex: Story = {
decorators: [
Story =>
ScopeDecorator(Story, {
mockCompileOnLoad: true,
providers: {
FileTreePathProvider,
EditorOpenDocProvider: LatexEditorOpenDocProvider,
},
}),
(Story, { globals }) => {
// FIXME: useScope has no effect
useScope({
rootFolder: {
name: 'rootFolder',
id: 'root-folder-id',
type: 'folder',
children: [
{
name: 'example.tex.tex',
id: 'example-doc-id',
type: 'doc',
selected: false,
$$hashKey: 'object:89',
},
{
name: 'frog.jpg',
id: 'frog-image-id',
type: 'file',
linkedFileData: null,
created: '2023-05-04T16:11:04.352Z',
$$hashKey: 'object:108',
},
],
selected: false,
$$hashKey: 'object:89',
},
{
name: 'frog.jpg',
id: 'frog-image-id',
type: 'file',
linkedFileData: null,
created: '2023-05-04T16:11:04.352Z',
$$hashKey: 'object:108',
settings: {
...settings,
overallTheme: globals.theme === 'default-' ? '' : globals.theme,
},
],
selected: false,
},
settings: {
...settings,
overallTheme: theme === 'default-' ? '' : theme,
},
permissions,
})
permissions,
})
useMeta({
'ol-showSymbolPalette': true,
})
useMeta({
'ol-showSymbolPalette': true,
})
return <SourceEditor />
return <Story />
},
],
}
export const Markdown = (args: any, { globals: { theme } }: any) => {
useScope({
editor: {
sharejs_doc: mockDoc(content.md, changes.md),
open_doc_name: 'example.md',
},
settings: {
...settings,
overallTheme: theme === 'default-' ? '' : theme,
},
permissions,
})
export const Markdown: Story = {
decorators: [
Story =>
ScopeDecorator(Story, {
mockCompileOnLoad: true,
providers: {
FileTreePathProvider,
EditorOpenDocProvider: MarkdownEditorOpenDocProvider,
},
}),
return <SourceEditor />
(Story, { globals }) => {
// FIXME: useScope has no effect
useScope({
settings: {
...settings,
overallTheme: globals.theme === 'default-' ? '' : globals.theme,
},
permissions,
})
return <Story />
},
],
}
export const Visual = (args: any, { globals: { theme } }: any) => {
useScope({
editor: {
sharejs_doc: mockDoc(content.tex, changes.tex),
open_doc_name: 'example.tex',
showVisual: true,
},
settings: {
...settings,
overallTheme: theme === 'default-' ? '' : theme,
},
permissions,
})
useMeta({
'ol-showSymbolPalette': true,
'ol-mathJaxPath': 'https://unpkg.com/mathjax@3.2.2/es5/tex-svg-full.js',
'ol-project_id': '63e21c07946dd8c76505f85a',
})
export const Visual: Story = {
decorators: [
Story =>
ScopeDecorator(Story, {
mockCompileOnLoad: true,
providers: {
FileTreePathProvider,
EditorOpenDocProvider: LatexEditorOpenDocProvider,
},
}),
return <SourceEditor />
(Story, { globals }) => {
// FIXME: useScope has no effect, so this does not default to the visual editor
useScope({
editor: {
showVisual: true,
},
settings: {
...settings,
overallTheme: globals.theme === 'default-' ? '' : globals.theme,
},
permissions,
})
useMeta({
'ol-showSymbolPalette': true,
'ol-mathJaxPath': 'https://unpkg.com/mathjax@3.2.2/es5/tex-svg-full.js',
'ol-project_id': '63e21c07946dd8c76505f85a',
})
return <Story />
},
],
}
export const Bibtex = (args: any, { globals: { theme } }: any) => {
useScope({
editor: {
sharejs_doc: mockDoc(content.bib, changes.bib),
open_doc_name: 'example.bib',
},
settings: {
...settings,
overallTheme: theme === 'default-' ? '' : theme,
},
permissions,
})
export const Bibtex: Story = {
decorators: [
Story =>
ScopeDecorator(Story, {
mockCompileOnLoad: true,
providers: {
FileTreePathProvider,
EditorOpenDocProvider: BibtexEditorOpenDocProvider,
},
}),
return <SourceEditor />
(Story, { globals }) => {
// FIXME: useScope has no effect
useScope({
settings: {
...settings,
overallTheme: globals.theme === 'default-' ? '' : globals.theme,
},
permissions,
})
return <Story />
},
],
}
const MAX_DOC_LENGTH = 2 * 1024 * 1024 // ol-maxDocLength

View File

@@ -11,6 +11,7 @@ import {
import { EditorView } from '@codemirror/view'
import { OpenDocuments } from '@/features/ide-react/editor/open-documents'
import { LogEntry } from '@/features/pdf-preview/util/types'
import { EditorViewContext } from '@/features/ide-react/context/editor-view-context'
describe('<PdfLogsEntries/>', function () {
const fakeFindEntityResult: FindResult = {
@@ -48,6 +49,19 @@ describe('<PdfLogsEntries/>', function () {
)
}
const EditorViewProvider: FC<React.PropsWithChildren> = ({ children }) => {
const value = {
view: new EditorView({ doc: '\\documentclass{article}' }),
setView: cy.stub(),
}
return (
<EditorViewContext.Provider value={value}>
{children}
</EditorViewContext.Provider>
)
}
const logEntries: LogEntry[] = [
{
file: 'main.tex',
@@ -62,10 +76,6 @@ describe('<PdfLogsEntries/>', function () {
},
]
const scope = {
'editor.view': new EditorView({ doc: '\\documentclass{article}' }),
}
beforeEach(function () {
cy.interceptCompile()
cy.interceptEvents()
@@ -73,7 +83,7 @@ describe('<PdfLogsEntries/>', function () {
it('displays human readable hint', function () {
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders providers={{ EditorViewProvider }}>
<PdfLogsEntries entries={logEntries} />
</EditorProviders>
)
@@ -84,8 +94,11 @@ describe('<PdfLogsEntries/>', function () {
it('opens doc on click', function () {
cy.mount(
<EditorProviders
scope={scope}
providers={{ EditorManagerProvider, FileTreePathProvider }}
providers={{
EditorManagerProvider,
FileTreePathProvider,
EditorViewProvider,
}}
>
<PdfLogsEntries entries={logEntries} />
</EditorProviders>
@@ -114,8 +127,11 @@ describe('<PdfLogsEntries/>', function () {
cy.mount(
<EditorProviders
scope={scope}
providers={{ EditorManagerProvider, FileTreePathProvider }}
providers={{
EditorManagerProvider,
FileTreePathProvider,
EditorViewProvider,
}}
>
<PdfLogsEntries entries={logEntries} />
</EditorProviders>
@@ -154,8 +170,11 @@ describe('<PdfLogsEntries/>', function () {
cy.mount(
<EditorProviders
scope={scope}
providers={{ EditorManagerProvider, FileTreePathProvider }}
providers={{
EditorManagerProvider,
FileTreePathProvider,
EditorViewProvider,
}}
>
<PdfLogsEntries entries={logEntries} />
</EditorProviders>

View File

@@ -3,7 +3,10 @@ import { cloneDeep } from 'lodash'
import { useDetachCompileContext as useCompileContext } from '../../../../frontend/js/shared/context/detach-compile-context'
import { useFileTreeData } from '../../../../frontend/js/shared/context/file-tree-data-context'
import { useEffect } from 'react'
import { EditorProviders } from '../../helpers/editor-providers'
import {
EditorProviders,
makeEditorOpenDocProvider,
} from '../../helpers/editor-providers'
import { mockScope } from './scope'
import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
import { FindResult } from '@/features/file-tree/util/path'
@@ -73,6 +76,22 @@ const WithSelectedEntities = ({
return null
}
function mockProviders() {
return {
EditorOpenDocProvider: makeEditorOpenDocProvider({
openDocName: 'main.tex',
currentDocument: {
doc_id: 'test-doc',
getSnapshot: () => 'some doc content',
hasBufferedOps: () => false,
on: () => {},
off: () => {},
leaveAndCleanUpPromise: () => Promise.resolve(),
},
}),
}
}
describe('<PdfSynctexControls/>', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-project_id', 'test-project')
@@ -84,9 +103,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
<PdfSynctexControls />
@@ -145,9 +165,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<WithSelectedEntities
mockSelectedEntities={
@@ -169,9 +190,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<WithSelectedEntities
mockSelectedEntities={[{ type: 'fileRef' }] as FindResult[]}
@@ -196,9 +218,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
<PdfSynctexControls />
@@ -218,9 +241,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
<PdfSynctexControls />
@@ -279,9 +303,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
<PdfSynctexControls />
@@ -317,9 +342,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<PdfSynctexControls />
</EditorProviders>
@@ -338,9 +364,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<PdfSynctexControls />
</EditorProviders>
)
@@ -385,9 +412,10 @@ describe('<PdfSynctexControls/>', function () {
cy.interceptCompile()
const scope = mockScope()
const providers = mockProviders()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} providers={providers}>
<WithPosition mockPosition={mockPosition} />
<PdfSynctexControls />
</EditorProviders>

View File

@@ -6,7 +6,6 @@ export const mockScope = () => ({
pdfViewer: 'pdfjs',
},
editor: {
open_doc_name: 'main.tex',
sharejs_doc: {
doc_id: 'test-doc',
getSnapshot: () => 'some doc content',

View File

@@ -12,6 +12,7 @@ import {
USER_ID,
} from '../../../helpers/editor-providers'
import { location } from '@/shared/components/location'
import useScopeValue from '@/shared/hooks/use-scope-value'
async function changePrivilegeLevel(screen, { current, next }) {
const select = screen.getByDisplayValue(current)
@@ -820,7 +821,14 @@ describe('<ShareProjectModal/>', function () {
fetchMock.get(`/project/${project._id}/tokens`, {})
fetchMock.post('express:/project/:projectId/settings/admin', 204)
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
let setPublicAccessLevel = function () {}
function WrappedModal() {
setPublicAccessLevel = useScopeValue('project.publicAccesLevel')[1]
return <ShareProjectModal {...modalProps} />
}
renderWithEditorContext(<WrappedModal />, {
scope: {
project: { ...project, publicAccesLevel: 'private' },
},
@@ -839,13 +847,10 @@ describe('<ShareProjectModal/>', function () {
publicAccessLevel: 'tokenBased',
})
// NOTE: updating the scoped project data manually,
// as the project data is usually updated via the websocket connection
window.overleaf.unstable.store.set('project', {
...project,
publicAccesLevel: 'tokenBased',
})
// watchCallbacks.project({ ...project, publicAccesLevel: 'tokenBased' })
// NOTE: the project data is usually updated via the websocket connection
// but we can't do that so we're doing it via the scope value store (this
// will be via the project context when this value has been migrated)
setPublicAccessLevel('tokenBased')
await screen.findByText('Link sharing is on')
const disableButton = await screen.findByRole('button', {
@@ -859,13 +864,7 @@ describe('<ShareProjectModal/>', function () {
publicAccessLevel: 'private',
})
// NOTE: updating the scoped project data manually,
// as the project data is usually updated via the websocket connection
window.overleaf.unstable.store.set('project', {
...project,
publicAccesLevel: 'private',
})
// watchCallbacks.project({ ...project, publicAccesLevel: 'private' })
setPublicAccessLevel('private')
await screen.findByText('Link sharing is off')
})

View File

@@ -17,8 +17,8 @@ export const mockScope = (
return {
editor: {
sharejs_doc: mockDoc(content, docOptions),
open_doc_name: 'test.tex',
open_doc_id: docId,
openDocName: 'test.tex',
currentDocumentId: docId,
showVisual: false,
wantTrackChanges: false,
},

View File

@@ -9,12 +9,15 @@ import {
IdeReactContext,
} from '@/features/ide-react/context/ide-react-context'
import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
import { ConnectionContext } from '@/features/ide-react/context/connection-context'
import { EditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
import useEventListener from '@/shared/hooks/use-event-listener'
import useDetachLayout from '@/shared/hooks/use-detach-layout'
import { LayoutContext } from '@/shared/context/layout-context'
import useExposedState from '@/shared/hooks/use-exposed-state'
// these constants can be imported in tests instead of
// using magic strings
@@ -119,6 +122,8 @@ export function EditorProviders({
off: () => {},
leaveAndCleanUpPromise: async () => {},
},
openDocName: null,
currentDocumentId: null,
},
project: {
_id: projectId,
@@ -144,6 +149,11 @@ export function EditorProviders({
providers={{
ConnectionProvider: makeConnectionProvider(socket),
IdeReactProvider: makeIdeReactProvider(scope, socket),
EditorOpenDocProvider: makeEditorOpenDocProvider({
openDocId: scope.editor.currentDocumentId,
openDocName: scope.editor.openDocName,
currentDocument: scope.editor.sharejs_doc,
}),
LayoutProvider: makeLayoutProvider(layoutContext),
...providers,
}}
@@ -202,15 +212,16 @@ const makeIdeReactProvider = (scope, socket) => {
// TODO: path for nested entries
scopeStore.set(key, value)
}
scopeStore.set('editor.sharejs_doc', scope.editor.sharejs_doc)
const scopeEventEmitter = new ReactScopeEventEmitter(
new IdeEventEmitter()
)
const unstableStore = new ReactScopeValueStore()
return {
socket,
scopeStore,
scopeEventEmitter,
unstableStore,
}
})
@@ -219,10 +230,10 @@ const makeIdeReactProvider = (scope, socket) => {
...window.overleaf,
unstable: {
...window.overleaf?.unstable,
store: ideContextValue.scopeStore,
store: ideContextValue.unstableStore,
},
}
}, [ideContextValue.scopeStore])
}, [ideContextValue.unstableStore])
return (
<IdeReactContext.Provider value={ideReactContextValue}>
@@ -235,6 +246,44 @@ const makeIdeReactProvider = (scope, socket) => {
return IdeReactProvider
}
export function makeEditorOpenDocProvider(initialValues) {
const {
currentDocumentId: initialCurrentDocumentId,
openDocName: initialOpenDocName,
currentDocument: initialCurrentDocument,
} = initialValues
const EditorOpenDocProvider = ({ children }) => {
const [currentDocumentId, setCurrentDocumentId] = useExposedState(
initialCurrentDocumentId,
'editor.open_doc_id'
)
const [openDocName, setOpenDocName] = useExposedState(
initialOpenDocName,
'editor.open_doc_name'
)
const [currentDocument, setCurrentDocument] = useState(
initialCurrentDocument
)
const value = {
currentDocumentId,
setCurrentDocumentId,
openDocName,
setOpenDocName,
currentDocument,
setCurrentDocument,
}
return (
<EditorOpenDocContext.Provider value={value}>
{children}
</EditorOpenDocContext.Provider>
)
}
return EditorOpenDocProvider
}
const makeLayoutProvider = layoutContextOverrides => {
const layout = {
...layoutContextDefault,