From f56675da34591069d6c55773ec0052fdb677e4df Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 2 Jul 2025 10:58:38 +0200 Subject: [PATCH] [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 --- .../features/pdf-preview/util/output-files.js | 4 +++ .../shared/context/local-compile-context.tsx | 34 ++++++------------- .../components/codemirror-editor.spec.tsx | 30 ++++++++++++++-- .../source-editor/helpers/mock-scope.ts | 3 -- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/services/web/frontend/js/features/pdf-preview/util/output-files.js b/services/web/frontend/js/features/pdf-preview/util/output-files.js index 3ee0dc1180..0903ec10e3 100644 --- a/services/web/frontend/js/features/pdf-preview/util/output-files.js +++ b/services/web/frontend/js/features/pdf-preview/util/output-files.js @@ -143,6 +143,10 @@ export const handleLogFiles = async (outputFiles, data, signal) => { return result } +/** + * @typedef {import('../../../../../types/annotation').Annotation} Annotation + * @returns {Record} + */ export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) { const rootDocDirname = dirname(fileTreeData, rootDocId) diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index 8c8a7b4885..af1430ac05 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -11,7 +11,6 @@ import { SetStateAction, } from 'react' import useScopeValue from '../hooks/use-scope-value' -import useScopeValueSetterOnly from '../hooks/use-scope-value-setter-only' import usePersistedState from '../hooks/use-persisted-state' import useAbortController from '../hooks/use-abort-controller' import DocumentCompiler from '../../features/pdf-preview/util/compiler' @@ -51,6 +50,7 @@ import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { captureException } from '@/infrastructure/error-reporter' import OError from '@overleaf/o-error' import getMeta from '@/utils/meta' +import type { Annotation } from '../../../../types/annotation' type PdfFile = Record @@ -74,7 +74,7 @@ export type CompileContext = { warnings: LogEntry[] typesetting: LogEntry[] } - logEntryAnnotations?: Record + logEntryAnnotations?: Record outputFilesArchive?: string pdfDownloadUrl?: string pdfFile?: PdfFile @@ -98,7 +98,7 @@ export type CompileContext = { stopOnFirstError: boolean stopOnValidationError: boolean stoppedOnFirstError: boolean - uncompiled?: boolean + uncompiled: boolean validationIssues?: Record firstRenderDone: (metrics: { latencyFetch: number @@ -156,34 +156,22 @@ export const LocalCompileProvider: FC = ({ const [hasShortCompileTimeout, setHasShortCompileTimeout] = useState(false) // the log entries parsed from the compile output log - const [logEntries, setLogEntries] = useScopeValueSetterOnly('pdf.logEntries') + const [logEntries, setLogEntries] = useState() // annotations for display in the editor, built from the log entries - const [logEntryAnnotations, setLogEntryAnnotations] = useScopeValue( - 'pdf.logEntryAnnotations' - ) + const [logEntryAnnotations, setLogEntryAnnotations] = useState< + undefined | Record + >() // 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') - - // the URL for loading the PDF in the preview pane - const [, setPdfUrl] = useScopeValueSetterOnly('pdf.url') - // low level details for metrics const [pdfFile, setPdfFile] = useState() - 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. - const [uncompiled, setUncompiled] = useScopeValue('pdf.uncompiled') + const [uncompiled, setUncompiled] = useState(false) // whether a doc has been edited since the last compile started const [editedSinceCompileStarted, setEditedSinceCompileStarted] = @@ -294,7 +282,7 @@ export const LocalCompileProvider: FC = ({ const cleanupCompileResult = useCallback(() => { setPdfFile(undefined) - setLogEntries(null) + setLogEntries(undefined) setLogEntryAnnotations({}) }, [setPdfFile, setLogEntries, setLogEntryAnnotations]) @@ -513,8 +501,8 @@ export const LocalCompileProvider: FC = ({ // handle log files // asynchronous (TODO: cancel on new compile?) - setLogEntryAnnotations(null) - setLogEntries(null) + setLogEntryAnnotations(undefined) + setLogEntries(undefined) setRawLog(undefined) handleLogFiles(outputFiles, data, abortController.signal).then( diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx index 40693c2ec1..257cc96661 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx @@ -7,7 +7,9 @@ import { activeEditorLine } from '../helpers/active-editor-line' import { TestContainer } from '../helpers/test-container' import customLocalStorage from '@/infrastructure/local-storage' 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('', { scrollBehavior: false }, function () { beforeEach(function () { @@ -64,27 +66,39 @@ describe('', { scrollBehavior: false }, function () { it('renders annotations in the gutter', function () { const scope = mockScope() - scope.pdf.logEntryAnnotations = { + const logEntryAnnotations: Record = { [docId]: [ { + id: '1', + entryIndex: 1, row: 20, type: 'error', text: 'Another error', + firstOnLine: true, }, { + id: '2', + entryIndex: 2, row: 19, type: 'error', text: 'An error', + firstOnLine: true, }, { + id: '3', + entryIndex: 3, row: 20, type: 'warning', text: 'A warning on the same line', + firstOnLine: false, }, { + id: '4', + entryIndex: 4, row: 25, type: 'warning', text: 'Another warning', + firstOnLine: true, }, ], } @@ -93,9 +107,19 @@ describe('', { scrollBehavior: false }, function () { cy.clock() + const LocalCompileProvider: FC = ({ children }) => ( + // @ts-expect-error: not entering all the values for LocalCompileContext + + {children} + + ) cy.mount( - + diff --git a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts index 701e3bc4b3..4b7d0fb4a1 100644 --- a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts +++ b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts @@ -22,9 +22,6 @@ export const mockScope = ( showVisual: false, wantTrackChanges: false, }, - pdf: { - logEntryAnnotations: {}, - }, project: { _id: 'test-project', name: 'Test Project',