diff --git a/services/web/frontend/js/features/pdf-preview/util/compiler.js b/services/web/frontend/js/features/pdf-preview/util/compiler.js
index a9cded5350..504aa5983b 100644
--- a/services/web/frontend/js/features/pdf-preview/util/compiler.js
+++ b/services/web/frontend/js/features/pdf-preview/util/compiler.js
@@ -22,6 +22,7 @@ export default class DocumentCompiler {
setData,
setFirstRenderDone,
setError,
+ cleanupCompileResult,
signal,
}) {
this.project = project
@@ -30,6 +31,7 @@ export default class DocumentCompiler {
this.setData = setData
this.setFirstRenderDone = setFirstRenderDone
this.setError = setError
+ this.cleanupCompileResult = cleanupCompileResult
this.signal = signal
this.clsiServerId = null
@@ -100,9 +102,13 @@ export default class DocumentCompiler {
const { firstRenderDone } = trackPdfDownload(data, compileTimeClientE2E)
this.setFirstRenderDone(() => firstRenderDone)
data.options = options
+ if (data.clsiServerId) {
+ this.clsiServerId = data.clsiServerId
+ }
this.setData(data)
} catch (error) {
console.error(error)
+ this.cleanupCompileResult()
this.setError(error.info?.statusCode === 429 ? 'rate-limited' : 'error')
} finally {
this.setCompiling(false)
diff --git a/services/web/frontend/js/shared/context/compile-context.js b/services/web/frontend/js/shared/context/compile-context.js
index 20f616249e..e5d461000a 100644
--- a/services/web/frontend/js/shared/context/compile-context.js
+++ b/services/web/frontend/js/shared/context/compile-context.js
@@ -8,6 +8,7 @@ import {
} from 'react'
import PropTypes from 'prop-types'
import useScopeValue from '../hooks/use-scope-value'
+import useScopeValueSetterOnly from '../hooks/use-scope-value-setter-only'
import usePersistedState from '../hooks/use-persisted-state'
import useAbortController from '../hooks/use-abort-controller'
import DocumentCompiler from '../../features/pdf-preview/util/compiler'
@@ -37,10 +38,8 @@ CompileContext.Provider.propTypes = {
error: PropTypes.string,
fileList: PropTypes.object,
hasChanges: PropTypes.bool.isRequired,
- hasLintingError: PropTypes.bool,
highlights: PropTypes.arrayOf(PropTypes.object),
logEntries: PropTypes.object,
- logEntryAnnotations: PropTypes.object,
pdfDownloadUrl: PropTypes.string,
pdfUrl: PropTypes.string,
pdfViewer: PropTypes.string,
@@ -75,21 +74,21 @@ export function CompileProvider({ children }) {
const [compiling, setCompiling] = useState(false)
// the log entries parsed from the compile output log
- const [logEntries, setLogEntries] = useScopeValue('pdf.logEntries')
+ const [logEntries, setLogEntries] = useScopeValueSetterOnly('pdf.logEntries')
// annotations for display in the editor, built from the log entries
- const [logEntryAnnotations, setLogEntryAnnotations] = useScopeValue(
- 'pdf.logEntryAnnotations'
- )
+ const [, setLogEntryAnnotations] = useScopeValue('pdf.logEntryAnnotations')
// the PDF viewer
const [pdfViewer] = useScopeValue('settings.pdfViewer')
// the URL for downloading the PDF
- const [pdfDownloadUrl, setPdfDownloadUrl] = useScopeValue('pdf.downloadUrl')
+ const [pdfDownloadUrl, setPdfDownloadUrl] = useScopeValueSetterOnly(
+ 'pdf.downloadUrl'
+ )
// the URL for loading the PDF in the preview pane
- const [pdfUrl, setPdfUrl] = useScopeValue('pdf.url')
+ const [pdfUrl, setPdfUrl] = useScopeValueSetterOnly('pdf.url')
// the project is considered to be "uncompiled" if a doc has changed since the last compile started
const [uncompiled, setUncompiled] = useScopeValue('pdf.uncompiled')
@@ -161,6 +160,13 @@ export function CompileProvider({ children }) {
const { signal } = useAbortController()
+ const cleanupCompileResult = useCallback(() => {
+ setPdfUrl(null)
+ setPdfDownloadUrl(null)
+ setLogEntries(null)
+ setLogEntryAnnotations({})
+ }, [setPdfUrl, setPdfDownloadUrl, setLogEntries, setLogEntryAnnotations])
+
// the document compiler
const [compiler] = useState(() => {
return new DocumentCompiler({
@@ -170,6 +176,7 @@ export function CompileProvider({ children }) {
setData,
setFirstRenderDone,
setError,
+ cleanupCompileResult,
signal,
})
})
@@ -215,11 +222,6 @@ export function CompileProvider({ children }) {
if (data) {
if (data.clsiServerId) {
setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
- compiler.clsiServerId = data.clsiServerId
- }
-
- if (data.compileGroup) {
- compiler.compileGroup = data.compileGroup
}
if (data.outputFiles) {
@@ -304,7 +306,6 @@ export function CompileProvider({ children }) {
}
}
}, [
- compiler,
data,
ide,
hasPremiumCompile,
@@ -422,10 +423,8 @@ export function CompileProvider({ children }) {
error,
fileList,
hasChanges,
- hasLintingError,
highlights,
logEntries,
- logEntryAnnotations,
pdfDownloadUrl,
pdfUrl,
pdfViewer,
@@ -433,7 +432,6 @@ export function CompileProvider({ children }) {
rawLog,
recompileFromScratch,
setAutoCompile,
- setClearingCache,
setCompiling,
setDraft,
setError,
@@ -461,10 +459,8 @@ export function CompileProvider({ children }) {
error,
fileList,
hasChanges,
- hasLintingError,
highlights,
logEntries,
- logEntryAnnotations,
position,
pdfDownloadUrl,
pdfUrl,
@@ -474,7 +470,7 @@ export function CompileProvider({ children }) {
setAutoCompile,
setDraft,
setError,
- setHasLintingError,
+ setHasLintingError, // only for stories
setHighlights,
setPosition,
setStopOnValidationError,
diff --git a/services/web/frontend/js/shared/hooks/use-scope-value-setter-only.js b/services/web/frontend/js/shared/hooks/use-scope-value-setter-only.js
new file mode 100644
index 0000000000..05c2d1b864
--- /dev/null
+++ b/services/web/frontend/js/shared/hooks/use-scope-value-setter-only.js
@@ -0,0 +1,37 @@
+import PropTypes from 'prop-types'
+import { useCallback, useState } from 'react'
+import { useIdeContext } from '../context/ide-context'
+import _ from 'lodash'
+
+/**
+ * Similar to `useScopeValue`, but instead of creating a two-way binding, only
+ * changes in react-> angular direction are propagated, with `value` remaining
+ * local and independent of its value in the Angular scope.
+ *
+ * The interface is compatible with React.useState(), including
+ * the option of passing a function to the setter.
+ *
+ * @param {string} path - dot '.' path of a property in the Angular scope.
+ * @param {any} defaultValue
+ * @returns {[any, function]} - value and setter function tuple.
+ */
+export default function useScopeValueSetterOnly(path, defaultValue) {
+ const { $scope } = useIdeContext({
+ $scope: PropTypes.object.isRequired,
+ })
+
+ const [value, setValue] = useState(defaultValue)
+
+ const scopeSetter = useCallback(
+ newValue => {
+ setValue(val => {
+ const actualNewValue = _.isFunction(newValue) ? newValue(val) : newValue
+ $scope.$applyAsync(() => _.set($scope, path, actualNewValue))
+ return actualNewValue
+ })
+ },
+ [path, $scope]
+ )
+
+ return [value, scopeSetter]
+}
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js
index 0ffd0f498a..f3e5e2b8db 100644
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js
+++ b/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js
@@ -482,20 +482,12 @@ describe('', function () {
code: 'AWFUL_ERROR',
})
- const { rerender } = renderWithEditorContext(, { scope })
+ renderWithEditorContext(, { scope })
await screen.findByText('Something went wrong while rendering this PDF.')
expect(screen.queryByLabelText('Page 1')).to.not.exist
expect(nock.isDone()).to.be.true
-
- mockValidPdf()
-
- rerender()
-
- await screen.findByLabelText('Page 1')
- expect(screen.queryByText('Something went wrong while rendering this PDF.'))
- .to.not.exist
})
it('shows an error for a corrupt PDF', async function () {
@@ -506,19 +498,11 @@ describe('', function () {
.get(/^\/build\/output.pdf/)
.replyWithFile(200, corruptPDF)
- const { rerender } = renderWithEditorContext(, { scope })
+ renderWithEditorContext(, { scope })
await screen.findByText('Something went wrong while rendering this PDF.')
expect(screen.queryByLabelText('Page 1')).to.not.exist
expect(nock.isDone()).to.be.true
-
- mockValidPdf()
-
- rerender()
-
- await screen.findByLabelText('Page 1')
- expect(screen.queryByText('Something went wrong while rendering this PDF.'))
- .to.not.exist
})
})