diff --git a/services/web/frontend/js/features/pdf-preview/util/compiler.ts b/services/web/frontend/js/features/pdf-preview/util/compiler.ts index dfbf3c4641..f4ce9fda4a 100644 --- a/services/web/frontend/js/features/pdf-preview/util/compiler.ts +++ b/services/web/frontend/js/features/pdf-preview/util/compiler.ts @@ -1,4 +1,3 @@ -import { isMainFile } from './editor-files' import getMeta from '../../../utils/meta' import { deleteJSON, postJSON } from '../../../infrastructure/fetch-json' import { debounce, DebouncedFunc } from 'lodash' @@ -8,9 +7,9 @@ import { debugConsole } from '@/utils/debugging' import { signalWithTimeout } from '@/utils/abort-signal' import { Dispatch, SetStateAction } from 'react' import { OpenDocuments } from '@/features/ide-react/editor/open-documents' -import { DocumentContainer } from '@/features/ide-react/editor/document-container' import { CompileOptions, CompileResponseData } from '@ol-types/compile' import { DeliveryLatencies } from './types' +import { RootDocInfo } from '@/shared/hooks/use-root-doc' const AUTO_COMPILE_MAX_WAIT = 5000 // We add a 2 second debounce to sending user changes to server if they aren't @@ -36,14 +35,12 @@ export default class DocumentCompiler { cleanupCompileResult: () => void signal: AbortSignal openDocs: OpenDocuments - projectRootDocId: string | null clsiServerId: string | null - currentDoc: DocumentContainer | null error: Error | undefined timer: number defaultOptions: CompileOptions debouncedAutoCompile: DebouncedFunc<() => void> - pathInFolder: (docId: string) => string | null + getRootDocInfo: () => RootDocInfo constructor({ compilingRef, @@ -57,6 +54,7 @@ export default class DocumentCompiler { cleanupCompileResult, signal, openDocs, + getRootDocInfo, }: { compilingRef: React.MutableRefObject projectId: string @@ -69,6 +67,7 @@ export default class DocumentCompiler { cleanupCompileResult: () => void signal: AbortSignal openDocs: OpenDocuments + getRootDocInfo: () => RootDocInfo }) { this.compilingRef = compilingRef this.projectId = projectId @@ -81,11 +80,9 @@ export default class DocumentCompiler { this.cleanupCompileResult = cleanupCompileResult this.signal = signal this.openDocs = openDocs - this.pathInFolder = () => null + this.getRootDocInfo = getRootDocInfo - this.projectRootDocId = null this.clsiServerId = null - this.currentDoc = null this.error = undefined this.timer = 0 this.defaultOptions = { @@ -136,9 +133,7 @@ export default class DocumentCompiler { const t0 = performance.now() - const rootDocId = this.getRootDocOverrideId() || this.projectRootDocId - const rootResourcePath = - (rootDocId && this.pathInFolder(rootDocId)) || 'main.tex' + const { rootDocId, rootResourcePath } = this.getRootDocInfo() const body = { rootDoc_id: rootDocId, @@ -184,21 +179,6 @@ export default class DocumentCompiler { } } - // parse the text of the current doc in the editor - // if it contains "\documentclass" then use this as the root doc - getRootDocOverrideId() { - // only override when not in the root doc itself - if (this.currentDoc && this.currentDoc.doc_id !== this.projectRootDocId) { - const snapshot = this.currentDoc.getSnapshot() - - if (snapshot && isMainFile(snapshot)) { - return this.currentDoc.doc_id - } - } - - return null - } - // build the query parameters added to post-compile requests buildPostCompileParams() { const params = new URLSearchParams() 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 22fb931863..87bfc642d1 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -34,6 +34,7 @@ import { useUserContext } from './user-context' import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { useDetachContext } from '@/shared/context/detach-context' import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' +import { useRootDoc } from '@/shared/hooks/use-root-doc' 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' @@ -151,7 +152,8 @@ export const LocalCompileProvider: FC = ({ const { features, alphaProgram } = useUserContext() const { fileTreeData } = useFileTreeData() - const { pathInFolder, findEntityByPath } = useFileTreePathContext() + const { findEntityByPath } = useFileTreePathContext() + const getRootDocInfo = useRootDoc() // whether a compile is in progress const [compiling, setCompiling] = useState(false) @@ -332,22 +334,14 @@ export const LocalCompileProvider: FC = ({ compilingRef, signal, openDocs, + getRootDocInfo, }) }) - // keep currentDoc in sync with the compiler + // keep the root doc lookup in sync with the compiler useEffect(() => { - compiler.currentDoc = currentDocument - }, [compiler, currentDocument]) - - // keep the project rootDocId in sync with the compiler - useEffect(() => { - compiler.projectRootDocId = rootDocId || null - }, [compiler, rootDocId]) - // keep pathInFolder in sync with the compiler - useEffect(() => { - compiler.pathInFolder = pathInFolder - }, [compiler, pathInFolder]) + compiler.getRootDocInfo = getRootDocInfo + }, [compiler, getRootDocInfo]) // keep draft setting in sync with the compiler useEffect(() => { @@ -399,9 +393,8 @@ export const LocalCompileProvider: FC = ({ dataFromCache.rootDocId = findEntityByPath( dataFromCache.options?.rootResourcePath || '' )?.entity?._id - const rootDocOverride = compiler.getRootDocOverrideId() || rootDocId settingsUpToDate = - rootDocOverride === dataFromCache.rootDocId && + getRootDocInfo().rootDocId === dataFromCache.rootDocId && dataFromCache.options.draft === draft && // Allow stopOnFirstError to be enabled in the compile from cache and disabled locally. // Compiles that passed with stopOnFirstError=true will also pass with stopOnFirstError=false. The inverse does not hold, and we need to recompile. @@ -428,9 +421,8 @@ export const LocalCompileProvider: FC = ({ joinedOnce, currentDocument, compiledOnce, - rootDocId, findEntityByPath, - compiler, + getRootDocInfo, compilerName, imageName, stopOnFirstError, @@ -552,7 +544,7 @@ export const LocalCompileProvider: FC = ({ result.logEntries.all ) as Record - const rootDocId = data.rootDocId || compiler.projectRootDocId + const rootDocId = data.rootDocId const previousRuleCounts = previousRuleCountsRef.current previousRuleCountsRef.current = { ruleCounts, rootDocId } @@ -654,7 +646,6 @@ export const LocalCompileProvider: FC = ({ setLogEntries, setLogEntryAnnotations, setPdfFile, - compiler, ]) // switch to logs if there's an error diff --git a/services/web/frontend/js/shared/hooks/use-root-doc.ts b/services/web/frontend/js/shared/hooks/use-root-doc.ts new file mode 100644 index 0000000000..b76437a4fe --- /dev/null +++ b/services/web/frontend/js/shared/hooks/use-root-doc.ts @@ -0,0 +1,33 @@ +import { useCallback } from 'react' +import { useProjectContext } from '@/shared/context/project-context' +import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context' +import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' +import { isMainFile } from '@/features/pdf-preview/util/editor-files' + +export type RootDocInfo = { + rootDocId: string | null + rootResourcePath: string +} + +export function useRootDoc(): () => RootDocInfo { + const { project } = useProjectContext() + const projectRootDocId = project?.rootDocId ?? null + const { currentDocument } = useEditorOpenDocContext() + const { pathInFolder } = useFileTreePathContext() + + // Inspecting the document content is too expensive to perform on every update. + // Compute the rootDoc on-demand instead. + return useCallback((): RootDocInfo => { + let rootDocId: string | null = projectRootDocId + if ( + currentDocument && + currentDocument.doc_id !== projectRootDocId && + isMainFile(currentDocument.getSnapshot()) + ) { + rootDocId = currentDocument.doc_id + } + const rootResourcePath = + (rootDocId && pathInFolder(rootDocId)) || 'main.tex' + return { rootDocId, rootResourcePath } + }, [projectRootDocId, currentDocument, pathInFolder]) +}