local-compile-context.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. import {
  2. FC,
  3. createContext,
  4. useCallback,
  5. useContext,
  6. useEffect,
  7. useMemo,
  8. useRef,
  9. useState,
  10. Dispatch,
  11. SetStateAction,
  12. } from 'react'
  13. import useScopeValue from '../hooks/use-scope-value'
  14. import useScopeValueSetterOnly from '../hooks/use-scope-value-setter-only'
  15. import usePersistedState from '../hooks/use-persisted-state'
  16. import useAbortController from '../hooks/use-abort-controller'
  17. import DocumentCompiler from '../../features/pdf-preview/util/compiler'
  18. import {
  19. send,
  20. sendMB,
  21. sendMBOnce,
  22. sendMBSampled,
  23. } from '../../infrastructure/event-tracking'
  24. import {
  25. buildLogEntryAnnotations,
  26. buildRuleCounts,
  27. buildRuleDeltas,
  28. handleLogFiles,
  29. handleOutputFiles,
  30. } from '../../features/pdf-preview/util/output-files'
  31. import { useProjectContext } from './project-context'
  32. import { useEditorContext } from './editor-context'
  33. import { buildFileList } from '../../features/pdf-preview/util/file-list'
  34. import { useLayoutContext } from './layout-context'
  35. import { useUserContext } from './user-context'
  36. import { useFileTreeData } from '@/shared/context/file-tree-data-context'
  37. import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
  38. import { useUserSettingsContext } from '@/shared/context/user-settings-context'
  39. import { useFeatureFlag } from '@/shared/context/split-test-context'
  40. import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
  41. import {
  42. PdfScrollPosition,
  43. usePdfScrollPosition,
  44. } from '@/shared/hooks/use-pdf-scroll-position'
  45. type PdfFile = Record<string, any>
  46. export type CompileContext = {
  47. autoCompile: boolean
  48. clearingCache: boolean
  49. clsiServerId?: string
  50. codeCheckFailed: boolean
  51. compiling: boolean
  52. deliveryLatencies: Record<string, any>
  53. draft: boolean
  54. error?: string
  55. fileList?: Record<string, any>
  56. hasChanges: boolean
  57. hasShortCompileTimeout: boolean
  58. highlights?: Record<string, any>[]
  59. isProjectOwner: boolean
  60. logEntries?: Record<string, any>
  61. logEntryAnnotations?: Record<string, any>
  62. outputFilesArchive?: string
  63. pdfDownloadUrl?: string
  64. pdfFile?: PdfFile
  65. pdfUrl?: string
  66. pdfViewer?: string
  67. position?: PdfScrollPosition
  68. rawLog?: string
  69. setAutoCompile: (value: boolean) => void
  70. setDraft: (value: any) => void
  71. setError: (value: any) => void
  72. setHasLintingError: (value: any) => void // only for storybook
  73. setHighlights: (value: any) => void
  74. setPosition: Dispatch<SetStateAction<PdfScrollPosition>>
  75. setShowCompileTimeWarning: (value: any) => void
  76. setShowLogs: (value: boolean) => void
  77. toggleLogs: () => void
  78. setStopOnFirstError: (value: boolean) => void
  79. setStopOnValidationError: (value: boolean) => void
  80. showCompileTimeWarning: boolean
  81. showLogs: boolean
  82. stopOnFirstError: boolean
  83. stopOnValidationError: boolean
  84. stoppedOnFirstError: boolean
  85. uncompiled?: boolean
  86. validationIssues?: Record<string, any>
  87. firstRenderDone: (metrics: {
  88. latencyFetch: number
  89. latencyRender: number | undefined
  90. pdfCachingMetrics: { viewerId: string }
  91. }) => void
  92. cleanupCompileResult?: () => void
  93. animateCompileDropdownArrow: boolean
  94. editedSinceCompileStarted: boolean
  95. lastCompileOptions: any
  96. setAnimateCompileDropdownArrow: (value: boolean) => void
  97. recompileFromScratch: () => void
  98. setCompiling: (value: boolean) => void
  99. startCompile: (options?: any) => void
  100. stopCompile: () => void
  101. setChangedAt: (value: any) => void
  102. clearCache: () => void
  103. syncToEntry: (value: any, keepCurrentView?: boolean) => void
  104. recordAction: (action: string) => void
  105. }
  106. export const LocalCompileContext = createContext<CompileContext | undefined>(
  107. undefined
  108. )
  109. export const LocalCompileProvider: FC = ({ children }) => {
  110. const { hasPremiumCompile, isProjectOwner } = useEditorContext()
  111. const { openDocWithId, openDocs, currentDocument } = useEditorManagerContext()
  112. const { _id: projectId, rootDocId } = useProjectContext()
  113. const { pdfPreviewOpen } = useLayoutContext()
  114. const { features, alphaProgram, labsProgram } = useUserContext()
  115. const { fileTreeData } = useFileTreeData()
  116. const { findEntityByPath } = useFileTreePathContext()
  117. // whether a compile is in progress
  118. const [compiling, setCompiling] = useState(false)
  119. // whether to show the compile time warning
  120. const [showCompileTimeWarning, setShowCompileTimeWarning] = useState(false)
  121. const [hasShortCompileTimeout, setHasShortCompileTimeout] = useState(false)
  122. // the log entries parsed from the compile output log
  123. const [logEntries, setLogEntries] = useScopeValueSetterOnly('pdf.logEntries')
  124. // annotations for display in the editor, built from the log entries
  125. const [logEntryAnnotations, setLogEntryAnnotations] = useScopeValue(
  126. 'pdf.logEntryAnnotations'
  127. )
  128. // the PDF viewer and whether syntax validation is enabled globally
  129. const { userSettings } = useUserSettingsContext()
  130. const { pdfViewer, syntaxValidation } = userSettings
  131. // the URL for downloading the PDF
  132. const [, setPdfDownloadUrl] =
  133. useScopeValueSetterOnly<string>('pdf.downloadUrl')
  134. // the URL for loading the PDF in the preview pane
  135. const [, setPdfUrl] = useScopeValueSetterOnly<string>('pdf.url')
  136. // low level details for metrics
  137. const [pdfFile, setPdfFile] = useState<PdfFile | undefined>()
  138. useEffect(() => {
  139. setPdfDownloadUrl(pdfFile?.pdfDownloadUrl)
  140. setPdfUrl(pdfFile?.pdfUrl)
  141. }, [pdfFile, setPdfDownloadUrl, setPdfUrl])
  142. // the project is considered to be "uncompiled" if a doc has changed, or finished saving, since the last compile started.
  143. const [uncompiled, setUncompiled] = useScopeValue('pdf.uncompiled')
  144. // whether a doc has been edited since the last compile started
  145. const [editedSinceCompileStarted, setEditedSinceCompileStarted] =
  146. useState(false)
  147. // the id of the CLSI server which ran the compile
  148. const [clsiServerId, setClsiServerId] = useState()
  149. // data received in response to a compile request
  150. const [data, setData] = useState<Record<string, any>>()
  151. // the rootDocId used in the most recent compile request, which may not be the
  152. // same as the project rootDocId. This is used to calculate correct paths when
  153. // parsing the compile logs
  154. const lastCompileRootDocId = data ? (data.rootDocId ?? rootDocId) : null
  155. // callback to be invoked for PdfJsMetrics
  156. const [firstRenderDone, setFirstRenderDone] = useState(() => () => {})
  157. // latencies of compile/pdf download/rendering
  158. const [deliveryLatencies, setDeliveryLatencies] = useState({})
  159. // whether the project has been compiled yet
  160. const [compiledOnce, setCompiledOnce] = useState(false)
  161. // whether the cache is being cleared
  162. const [clearingCache, setClearingCache] = useState(false)
  163. // whether the logs should be visible
  164. const [showLogs, setShowLogs] = useState(false)
  165. // whether the compile dropdown arrow should be animated
  166. const [animateCompileDropdownArrow, setAnimateCompileDropdownArrow] =
  167. useState(false)
  168. const toggleLogs = useCallback(() => {
  169. setShowLogs(prev => {
  170. if (!prev) {
  171. sendMBOnce('ide-open-logs-once')
  172. }
  173. return !prev
  174. })
  175. }, [setShowLogs])
  176. // an error that occurred
  177. const [error, setError] = useState<string>()
  178. // the list of files that can be downloaded
  179. const [fileList, setFileList] = useState<Record<string, any[]>>()
  180. // the raw contents of the log file
  181. const [rawLog, setRawLog] = useState<string>()
  182. // validation issues from CLSI
  183. const [validationIssues, setValidationIssues] = useState()
  184. // areas to highlight on the PDF, from synctex
  185. const [highlights, setHighlights] = useState()
  186. const [position, setPosition] = usePdfScrollPosition(lastCompileRootDocId)
  187. // whether autocompile is switched on
  188. const [autoCompile, setAutoCompile] = usePersistedState(
  189. `autocompile_enabled:${projectId}`,
  190. false,
  191. true
  192. )
  193. // whether the compile should run in draft mode
  194. const [draft, setDraft] = usePersistedState(`draft:${projectId}`, false, true)
  195. // whether compiling should stop on first error
  196. const [stopOnFirstError, setStopOnFirstError] = usePersistedState(
  197. `stop_on_first_error:${projectId}`,
  198. false,
  199. true
  200. )
  201. // whether the last compiles stopped on first error
  202. const [stoppedOnFirstError, setStoppedOnFirstError] = useState(false)
  203. // whether compiling should be prevented if there are linting errors
  204. const [stopOnValidationError, setStopOnValidationError] = usePersistedState(
  205. `stop_on_validation_error:${projectId}`,
  206. true,
  207. true
  208. )
  209. // whether the editor linter found errors
  210. const [hasLintingError, setHasLintingError] = useScopeValue('hasLintingError')
  211. // the timestamp that a doc was last changed
  212. const [changedAt, setChangedAt] = useState(0)
  213. const { signal } = useAbortController()
  214. const cleanupCompileResult = useCallback(() => {
  215. setPdfFile(undefined)
  216. setLogEntries(null)
  217. setLogEntryAnnotations({})
  218. }, [setPdfFile, setLogEntries, setLogEntryAnnotations])
  219. const compilingRef = useRef(false)
  220. useEffect(() => {
  221. compilingRef.current = compiling
  222. }, [compiling])
  223. const _buildLogEntryAnnotations = useCallback(
  224. entries =>
  225. buildLogEntryAnnotations(entries, fileTreeData, lastCompileRootDocId),
  226. [fileTreeData, lastCompileRootDocId]
  227. )
  228. const buildLogEntryAnnotationsRef = useRef(_buildLogEntryAnnotations)
  229. useEffect(() => {
  230. buildLogEntryAnnotationsRef.current = _buildLogEntryAnnotations
  231. }, [_buildLogEntryAnnotations])
  232. // the document compiler
  233. const [compiler] = useState(() => {
  234. return new DocumentCompiler({
  235. projectId,
  236. setChangedAt,
  237. setCompiling,
  238. setData,
  239. setFirstRenderDone,
  240. setDeliveryLatencies,
  241. setError,
  242. cleanupCompileResult,
  243. compilingRef,
  244. signal,
  245. openDocs,
  246. })
  247. })
  248. // keep currentDoc in sync with the compiler
  249. useEffect(() => {
  250. compiler.currentDoc = currentDocument
  251. }, [compiler, currentDocument])
  252. // keep the project rootDocId in sync with the compiler
  253. useEffect(() => {
  254. compiler.projectRootDocId = rootDocId
  255. }, [compiler, rootDocId])
  256. // keep draft setting in sync with the compiler
  257. useEffect(() => {
  258. compiler.setOption('draft', draft)
  259. }, [compiler, draft])
  260. // keep stop on first error setting in sync with the compiler
  261. useEffect(() => {
  262. compiler.setOption('stopOnFirstError', stopOnFirstError)
  263. }, [compiler, stopOnFirstError])
  264. useEffect(() => {
  265. setUncompiled(changedAt > 0)
  266. }, [setUncompiled, changedAt])
  267. useEffect(() => {
  268. setEditedSinceCompileStarted(changedAt > 0)
  269. }, [setEditedSinceCompileStarted, changedAt])
  270. // always compile the PDF once after opening the project, after the doc has loaded
  271. useEffect(() => {
  272. if (!compiledOnce && currentDocument) {
  273. setCompiledOnce(true)
  274. compiler.compile({ isAutoCompileOnLoad: true })
  275. }
  276. }, [compiledOnce, currentDocument, compiler])
  277. useEffect(() => {
  278. setHasShortCompileTimeout(
  279. features?.compileTimeout !== undefined && features.compileTimeout <= 60
  280. )
  281. }, [features])
  282. useEffect(() => {
  283. if (hasShortCompileTimeout && compiling && isProjectOwner) {
  284. const timeout = window.setTimeout(() => {
  285. setShowCompileTimeWarning(true)
  286. }, 30000)
  287. return () => {
  288. window.clearTimeout(timeout)
  289. }
  290. }
  291. }, [compiling, isProjectOwner, hasShortCompileTimeout])
  292. const hasCompileLogsEvents = useFeatureFlag('compile-log-events')
  293. // compare log entry counts with the previous compile, and record actions between compiles
  294. // these are refs rather than state so they don't trigger the effect to run
  295. const previousRuleCountsRef = useRef<{
  296. ruleCounts: Record<string, number>
  297. rootDocId: string
  298. } | null>(null)
  299. const recordedActionsRef = useRef<Record<string, boolean>>({})
  300. const recordAction = useCallback((action: string) => {
  301. recordedActionsRef.current[action] = true
  302. }, [])
  303. // handle the data returned from a compile request
  304. // note: this should _only_ run when `data` changes,
  305. // the other dependencies must all be static
  306. useEffect(() => {
  307. const abortController = new AbortController()
  308. const recordedActions = recordedActionsRef.current
  309. recordedActionsRef.current = {}
  310. if (data) {
  311. if (data.clsiServerId) {
  312. setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
  313. }
  314. if (data.outputFiles) {
  315. const outputFiles = new Map()
  316. for (const outputFile of data.outputFiles) {
  317. outputFiles.set(outputFile.path, outputFile)
  318. }
  319. // set the PDF context
  320. if (data.status === 'success') {
  321. setPdfFile(handleOutputFiles(outputFiles, projectId, data))
  322. }
  323. setFileList(
  324. buildFileList(
  325. outputFiles,
  326. data.clsiServerId,
  327. data.compileGroup,
  328. data.outputFilesArchive
  329. )
  330. )
  331. // handle log files
  332. // asynchronous (TODO: cancel on new compile?)
  333. setLogEntryAnnotations(null)
  334. setLogEntries(null)
  335. setRawLog(undefined)
  336. handleLogFiles(outputFiles, data, abortController.signal).then(
  337. (result: Record<string, any>) => {
  338. setRawLog(result.log)
  339. setLogEntries(result.logEntries)
  340. setLogEntryAnnotations(
  341. buildLogEntryAnnotationsRef.current(result.logEntries.all)
  342. )
  343. // sample compile stats for real users
  344. if (!alphaProgram) {
  345. if (['success', 'stopped-on-first-error'].includes(data.status)) {
  346. sendMBSampled(
  347. 'compile-result',
  348. {
  349. errors: result.logEntries.errors.length,
  350. warnings: result.logEntries.warnings.length,
  351. typesetting: result.logEntries.typesetting.length,
  352. newPdfPreview: true, // TODO: is this useful?
  353. stopOnFirstError: data.options.stopOnFirstError,
  354. },
  355. 0.01
  356. )
  357. }
  358. if (hasCompileLogsEvents || labsProgram) {
  359. const ruleCounts = buildRuleCounts(
  360. result.logEntries.all
  361. ) as Record<string, number>
  362. const rootDocId = data.rootDocId || compiler.projectRootDocId
  363. const previousRuleCounts = previousRuleCountsRef.current
  364. previousRuleCountsRef.current = { ruleCounts, rootDocId }
  365. const ruleDeltas =
  366. previousRuleCounts &&
  367. previousRuleCounts.rootDocId === rootDocId
  368. ? buildRuleDeltas(ruleCounts, previousRuleCounts.ruleCounts)
  369. : {}
  370. sendMB('compile-log-entries', {
  371. status: data.status,
  372. stopOnFirstError: data.options.stopOnFirstError,
  373. isAutoCompileOnLoad: !!data.options.isAutoCompileOnLoad,
  374. isAutoCompileOnChange: !!data.options.isAutoCompileOnChange,
  375. rootDocId,
  376. ...recordedActions,
  377. ...ruleCounts,
  378. ...ruleDeltas,
  379. })
  380. }
  381. }
  382. }
  383. )
  384. }
  385. switch (data.status) {
  386. case 'success':
  387. setError(undefined)
  388. setShowLogs(false)
  389. break
  390. case 'stopped-on-first-error':
  391. setError(undefined)
  392. setShowLogs(true)
  393. break
  394. case 'clsi-maintenance':
  395. case 'compile-in-progress':
  396. case 'exited':
  397. case 'failure':
  398. case 'project-too-large':
  399. case 'rate-limited':
  400. case 'terminated':
  401. case 'too-recently-compiled':
  402. setError(data.status)
  403. break
  404. case 'timedout':
  405. setError('timedout')
  406. if (!hasPremiumCompile && isProjectOwner) {
  407. send(
  408. 'subscription-funnel',
  409. 'editor-click-feature',
  410. 'compile-timeout'
  411. )
  412. }
  413. break
  414. case 'autocompile-backoff':
  415. if (!data.options.isAutoCompileOnLoad) {
  416. setError('autocompile-disabled')
  417. setAutoCompile(false)
  418. }
  419. break
  420. case 'unavailable':
  421. setError('clsi-unavailable')
  422. break
  423. case 'validation-problems':
  424. setError('validation-problems')
  425. setValidationIssues(data.validationProblems)
  426. break
  427. default:
  428. setError('error')
  429. break
  430. }
  431. setStoppedOnFirstError(data.status === 'stopped-on-first-error')
  432. }
  433. return () => {
  434. abortController.abort()
  435. }
  436. }, [
  437. data,
  438. alphaProgram,
  439. labsProgram,
  440. features,
  441. hasCompileLogsEvents,
  442. hasPremiumCompile,
  443. isProjectOwner,
  444. projectId,
  445. setAutoCompile,
  446. setClsiServerId,
  447. setLogEntries,
  448. setLogEntryAnnotations,
  449. setPdfFile,
  450. compiler,
  451. ])
  452. // switch to logs if there's an error
  453. useEffect(() => {
  454. if (error) {
  455. setShowLogs(true)
  456. }
  457. }, [error])
  458. // whether there has been an autocompile linting error, if syntax validation is switched on
  459. const autoCompileLintingError = Boolean(
  460. autoCompile && syntaxValidation && hasLintingError
  461. )
  462. const codeCheckFailed = stopOnValidationError && autoCompileLintingError
  463. // the project is available for auto-compiling
  464. // (autocompile is enabled, the PDF preview is open, and the code check (if enabled) hasn't failed)
  465. const canAutoCompile = Boolean(
  466. autoCompile && pdfPreviewOpen && !codeCheckFailed
  467. )
  468. // show that the project has pending changes
  469. const hasChanges = Boolean(canAutoCompile && uncompiled && compiledOnce)
  470. // call the debounced autocompile function if the project is available for auto-compiling and it has changed
  471. useEffect(() => {
  472. if (canAutoCompile) {
  473. if (changedAt > 0) {
  474. compiler.debouncedAutoCompile()
  475. }
  476. } else {
  477. compiler.debouncedAutoCompile.cancel()
  478. }
  479. }, [compiler, canAutoCompile, changedAt])
  480. // cancel debounced recompile on unmount
  481. useEffect(() => {
  482. return () => {
  483. compiler.debouncedAutoCompile.cancel()
  484. }
  485. }, [compiler])
  486. // start a compile manually
  487. const startCompile = useCallback(
  488. options => {
  489. compiler.compile(options)
  490. },
  491. [compiler]
  492. )
  493. // stop a compile manually
  494. const stopCompile = useCallback(() => {
  495. compiler.stopCompile()
  496. }, [compiler])
  497. // clear the compile cache
  498. const clearCache = useCallback(() => {
  499. setClearingCache(true)
  500. return compiler
  501. .clearCache()
  502. .then(() => {
  503. setFileList(undefined)
  504. setPdfFile(undefined)
  505. })
  506. .finally(() => {
  507. setClearingCache(false)
  508. })
  509. }, [compiler])
  510. const syncToEntry = useCallback(
  511. (entry, keepCurrentView = false) => {
  512. const result = findEntityByPath(entry.file)
  513. if (result && result.type === 'doc') {
  514. openDocWithId(result.entity._id, {
  515. gotoLine: entry.line ?? undefined,
  516. gotoColumn: entry.column ?? undefined,
  517. keepCurrentView,
  518. })
  519. }
  520. },
  521. [findEntityByPath, openDocWithId]
  522. )
  523. // clear the cache then run a compile, triggered by a menu item
  524. const recompileFromScratch = useCallback(() => {
  525. clearCache().then(() => {
  526. compiler.compile()
  527. })
  528. }, [clearCache, compiler])
  529. // After a compile, the compiler sets `data.options` to the options that were
  530. // used for that compile.
  531. const lastCompileOptions = useMemo(() => data?.options || {}, [data])
  532. useEffect(() => {
  533. const listener = (event: Event) => {
  534. setShowLogs((event as CustomEvent<boolean>).detail as boolean)
  535. }
  536. window.addEventListener('editor:show-logs', listener)
  537. return () => {
  538. window.removeEventListener('editor:show-logs', listener)
  539. }
  540. }, [])
  541. const value = useMemo(
  542. () => ({
  543. animateCompileDropdownArrow,
  544. autoCompile,
  545. clearCache,
  546. clearingCache,
  547. clsiServerId,
  548. codeCheckFailed,
  549. compiling,
  550. deliveryLatencies,
  551. draft,
  552. editedSinceCompileStarted,
  553. error,
  554. fileList,
  555. hasChanges,
  556. hasShortCompileTimeout,
  557. highlights,
  558. isProjectOwner,
  559. lastCompileOptions,
  560. logEntryAnnotations,
  561. logEntries,
  562. pdfDownloadUrl: pdfFile?.pdfDownloadUrl,
  563. pdfFile,
  564. pdfUrl: pdfFile?.pdfUrl,
  565. pdfViewer,
  566. position,
  567. rawLog,
  568. recompileFromScratch,
  569. setAnimateCompileDropdownArrow,
  570. setAutoCompile,
  571. setCompiling,
  572. setDraft,
  573. setError,
  574. setHasLintingError, // only for stories
  575. setHighlights,
  576. setPosition,
  577. showCompileTimeWarning,
  578. setShowCompileTimeWarning,
  579. setShowLogs,
  580. toggleLogs,
  581. setStopOnFirstError,
  582. setStopOnValidationError,
  583. showLogs,
  584. startCompile,
  585. stopCompile,
  586. stopOnFirstError,
  587. stopOnValidationError,
  588. stoppedOnFirstError,
  589. uncompiled,
  590. validationIssues,
  591. firstRenderDone,
  592. setChangedAt,
  593. cleanupCompileResult,
  594. syncToEntry,
  595. recordAction,
  596. }),
  597. [
  598. animateCompileDropdownArrow,
  599. autoCompile,
  600. clearCache,
  601. clearingCache,
  602. clsiServerId,
  603. codeCheckFailed,
  604. compiling,
  605. deliveryLatencies,
  606. draft,
  607. editedSinceCompileStarted,
  608. error,
  609. fileList,
  610. hasChanges,
  611. hasShortCompileTimeout,
  612. highlights,
  613. isProjectOwner,
  614. lastCompileOptions,
  615. logEntries,
  616. logEntryAnnotations,
  617. position,
  618. pdfFile,
  619. pdfViewer,
  620. rawLog,
  621. recompileFromScratch,
  622. setAnimateCompileDropdownArrow,
  623. setAutoCompile,
  624. setDraft,
  625. setError,
  626. setHasLintingError, // only for stories
  627. setHighlights,
  628. setPosition,
  629. setShowCompileTimeWarning,
  630. setStopOnFirstError,
  631. setStopOnValidationError,
  632. showCompileTimeWarning,
  633. showLogs,
  634. startCompile,
  635. stopCompile,
  636. stopOnFirstError,
  637. stopOnValidationError,
  638. stoppedOnFirstError,
  639. uncompiled,
  640. validationIssues,
  641. firstRenderDone,
  642. setChangedAt,
  643. cleanupCompileResult,
  644. setShowLogs,
  645. toggleLogs,
  646. syncToEntry,
  647. recordAction,
  648. ]
  649. )
  650. return (
  651. <LocalCompileContext.Provider value={value}>
  652. {children}
  653. </LocalCompileContext.Provider>
  654. )
  655. }
  656. export function useLocalCompileContext() {
  657. const context = useContext(LocalCompileContext)
  658. if (!context) {
  659. throw new Error(
  660. 'useLocalCompileContext is only available inside LocalCompileProvider'
  661. )
  662. }
  663. return context
  664. }