Bläddra i källkod

[web] add a hook for discovering the current root doc (#34027)

GitOrigin-RevId: d1930e6b13ca18dbae927dc15a5c6507351f71c8
Jakob Ackermann 2 månader sedan
förälder
incheckning
984b8e3f4a

+ 6 - 26
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<boolean>
     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()

+ 10 - 19
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<React.PropsWithChildren> = ({
   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<React.PropsWithChildren> = ({
       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<React.PropsWithChildren> = ({
       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<React.PropsWithChildren> = ({
     joinedOnce,
     currentDocument,
     compiledOnce,
-    rootDocId,
     findEntityByPath,
-    compiler,
+    getRootDocInfo,
     compilerName,
     imageName,
     stopOnFirstError,
@@ -552,7 +544,7 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
                   result.logEntries.all
                 ) as Record<string, number>
 
-                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<React.PropsWithChildren> = ({
     setLogEntries,
     setLogEntryAnnotations,
     setPdfFile,
-    compiler,
   ])
 
   // switch to logs if there's an error

+ 33 - 0
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])
+}