local-compile-context.tsx 22 KB

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