local-compile-context.tsx 22 KB

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