[web] Move pdf.* scopes to react states (#26599)

* Move `pdf.logEntryAnnotations` to react state

* Remove unused scope `pdf.downloadUrl`

* Remove unused scope `pdf.url`

* Move `pdf.uncompiled` to react state

* Move `pdf.logEntries` to react state

* Remove `pdf` from `mockScope`

* Fix test: "renders annotations in the gutter"

GitOrigin-RevId: bf1d0ec30cc0ffcc1177871651483c296ed08baf
This commit is contained in:
Antoine Clausse
2025-07-03 08:04:58 +00:00
committed by Copybot
parent 89bdcf59ab
commit f56675da34
4 changed files with 42 additions and 29 deletions
@@ -143,6 +143,10 @@ export const handleLogFiles = async (outputFiles, data, signal) => {
return result return result
} }
/**
* @typedef {import('../../../../../types/annotation').Annotation} Annotation
* @returns {Record<string, Annotation[]>}
*/
export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) { export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
const rootDocDirname = dirname(fileTreeData, rootDocId) const rootDocDirname = dirname(fileTreeData, rootDocId)
@@ -11,7 +11,6 @@ import {
SetStateAction, SetStateAction,
} from 'react' } from 'react'
import useScopeValue from '../hooks/use-scope-value' import useScopeValue from '../hooks/use-scope-value'
import useScopeValueSetterOnly from '../hooks/use-scope-value-setter-only'
import usePersistedState from '../hooks/use-persisted-state' import usePersistedState from '../hooks/use-persisted-state'
import useAbortController from '../hooks/use-abort-controller' import useAbortController from '../hooks/use-abort-controller'
import DocumentCompiler from '../../features/pdf-preview/util/compiler' import DocumentCompiler from '../../features/pdf-preview/util/compiler'
@@ -51,6 +50,7 @@ import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { captureException } from '@/infrastructure/error-reporter' import { captureException } from '@/infrastructure/error-reporter'
import OError from '@overleaf/o-error' import OError from '@overleaf/o-error'
import getMeta from '@/utils/meta' import getMeta from '@/utils/meta'
import type { Annotation } from '../../../../types/annotation'
type PdfFile = Record<string, any> type PdfFile = Record<string, any>
@@ -74,7 +74,7 @@ export type CompileContext = {
warnings: LogEntry[] warnings: LogEntry[]
typesetting: LogEntry[] typesetting: LogEntry[]
} }
logEntryAnnotations?: Record<string, any> logEntryAnnotations?: Record<string, Annotation[]>
outputFilesArchive?: string outputFilesArchive?: string
pdfDownloadUrl?: string pdfDownloadUrl?: string
pdfFile?: PdfFile pdfFile?: PdfFile
@@ -98,7 +98,7 @@ export type CompileContext = {
stopOnFirstError: boolean stopOnFirstError: boolean
stopOnValidationError: boolean stopOnValidationError: boolean
stoppedOnFirstError: boolean stoppedOnFirstError: boolean
uncompiled?: boolean uncompiled: boolean
validationIssues?: Record<string, any> validationIssues?: Record<string, any>
firstRenderDone: (metrics: { firstRenderDone: (metrics: {
latencyFetch: number latencyFetch: number
@@ -156,34 +156,22 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
const [hasShortCompileTimeout, setHasShortCompileTimeout] = useState(false) const [hasShortCompileTimeout, setHasShortCompileTimeout] = useState(false)
// the log entries parsed from the compile output log // the log entries parsed from the compile output log
const [logEntries, setLogEntries] = useScopeValueSetterOnly('pdf.logEntries') const [logEntries, setLogEntries] = useState<CompileContext['logEntries']>()
// annotations for display in the editor, built from the log entries // annotations for display in the editor, built from the log entries
const [logEntryAnnotations, setLogEntryAnnotations] = useScopeValue( const [logEntryAnnotations, setLogEntryAnnotations] = useState<
'pdf.logEntryAnnotations' undefined | Record<string, Annotation[]>
) >()
// the PDF viewer and whether syntax validation is enabled globally // the PDF viewer and whether syntax validation is enabled globally
const { userSettings } = useUserSettingsContext() const { userSettings } = useUserSettingsContext()
const { pdfViewer, syntaxValidation } = userSettings const { pdfViewer, syntaxValidation } = userSettings
// the URL for downloading the PDF
const [, setPdfDownloadUrl] =
useScopeValueSetterOnly<string>('pdf.downloadUrl')
// the URL for loading the PDF in the preview pane
const [, setPdfUrl] = useScopeValueSetterOnly<string>('pdf.url')
// low level details for metrics // low level details for metrics
const [pdfFile, setPdfFile] = useState<PdfFile | undefined>() const [pdfFile, setPdfFile] = useState<PdfFile | undefined>()
useEffect(() => {
setPdfDownloadUrl(pdfFile?.pdfDownloadUrl)
setPdfUrl(pdfFile?.pdfUrl)
}, [pdfFile, setPdfDownloadUrl, setPdfUrl])
// the project is considered to be "uncompiled" if a doc has changed, or finished saving, since the last compile started. // the project is considered to be "uncompiled" if a doc has changed, or finished saving, since the last compile started.
const [uncompiled, setUncompiled] = useScopeValue('pdf.uncompiled') const [uncompiled, setUncompiled] = useState(false)
// whether a doc has been edited since the last compile started // whether a doc has been edited since the last compile started
const [editedSinceCompileStarted, setEditedSinceCompileStarted] = const [editedSinceCompileStarted, setEditedSinceCompileStarted] =
@@ -294,7 +282,7 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
const cleanupCompileResult = useCallback(() => { const cleanupCompileResult = useCallback(() => {
setPdfFile(undefined) setPdfFile(undefined)
setLogEntries(null) setLogEntries(undefined)
setLogEntryAnnotations({}) setLogEntryAnnotations({})
}, [setPdfFile, setLogEntries, setLogEntryAnnotations]) }, [setPdfFile, setLogEntries, setLogEntryAnnotations])
@@ -513,8 +501,8 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
// handle log files // handle log files
// asynchronous (TODO: cancel on new compile?) // asynchronous (TODO: cancel on new compile?)
setLogEntryAnnotations(null) setLogEntryAnnotations(undefined)
setLogEntries(null) setLogEntries(undefined)
setRawLog(undefined) setRawLog(undefined)
handleLogFiles(outputFiles, data, abortController.signal).then( handleLogFiles(outputFiles, data, abortController.signal).then(
@@ -7,7 +7,9 @@ import { activeEditorLine } from '../helpers/active-editor-line'
import { TestContainer } from '../helpers/test-container' import { TestContainer } from '../helpers/test-container'
import customLocalStorage from '@/infrastructure/local-storage' import customLocalStorage from '@/infrastructure/local-storage'
import { OnlineUsersContext } from '@/features/ide-react/context/online-users-context' import { OnlineUsersContext } from '@/features/ide-react/context/online-users-context'
import { FC } from 'react' import { LocalCompileContext } from '@/shared/context/local-compile-context'
import type { FC, PropsWithChildren } from 'react'
import type { Annotation } from '../../../../../types/annotation'
describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () { describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
beforeEach(function () { beforeEach(function () {
@@ -64,27 +66,39 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
it('renders annotations in the gutter', function () { it('renders annotations in the gutter', function () {
const scope = mockScope() const scope = mockScope()
scope.pdf.logEntryAnnotations = { const logEntryAnnotations: Record<string, Annotation[]> = {
[docId]: [ [docId]: [
{ {
id: '1',
entryIndex: 1,
row: 20, row: 20,
type: 'error', type: 'error',
text: 'Another error', text: 'Another error',
firstOnLine: true,
}, },
{ {
id: '2',
entryIndex: 2,
row: 19, row: 19,
type: 'error', type: 'error',
text: 'An error', text: 'An error',
firstOnLine: true,
}, },
{ {
id: '3',
entryIndex: 3,
row: 20, row: 20,
type: 'warning', type: 'warning',
text: 'A warning on the same line', text: 'A warning on the same line',
firstOnLine: false,
}, },
{ {
id: '4',
entryIndex: 4,
row: 25, row: 25,
type: 'warning', type: 'warning',
text: 'Another warning', text: 'Another warning',
firstOnLine: true,
}, },
], ],
} }
@@ -93,9 +107,19 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
cy.clock() cy.clock()
const LocalCompileProvider: FC<PropsWithChildren> = ({ children }) => (
// @ts-expect-error: not entering all the values for LocalCompileContext
<LocalCompileContext.Provider value={{ logEntryAnnotations }}>
{children}
</LocalCompileContext.Provider>
)
cy.mount( cy.mount(
<TestContainer> <TestContainer>
<EditorProviders scope={scope} userSettings={userSettings}> <EditorProviders
scope={scope}
userSettings={userSettings}
providers={{ LocalCompileProvider }}
>
<CodeMirrorEditor /> <CodeMirrorEditor />
</EditorProviders> </EditorProviders>
</TestContainer> </TestContainer>
@@ -22,9 +22,6 @@ export const mockScope = (
showVisual: false, showVisual: false,
wantTrackChanges: false, wantTrackChanges: false,
}, },
pdf: {
logEntryAnnotations: {},
},
project: { project: {
_id: 'test-project', _id: 'test-project',
name: 'Test Project', name: 'Test Project',