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