Merge pull request #27640 from overleaf/mj-ide-compile-errors

[web] Handle premium timeout and running compilation in redesigned editor

GitOrigin-RevId: 8ed05c8b9c60e85605f374898b3176c7afa561a3
This commit is contained in:
Mathias Jakobsen
2025-08-08 08:06:52 +00:00
committed by Copybot
parent 9b601774fc
commit e27da3bfd8
8 changed files with 262 additions and 45 deletions
@@ -1,28 +1,61 @@
import OLButton from '@/features/ui/components/ol/ol-button'
import MaterialIcon from '@/shared/components/material-icon'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import { useRailContext } from '../../contexts/rail-context'
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
import { useIsNewEditorEnabled } from '../../utils/new-editor-utils'
import { upgradePlan } from '@/main/account-upgrade'
import classNames from 'classnames'
import { useStopOnFirstError } from '@/shared/hooks/use-stop-on-first-error'
import { useCallback } from 'react'
// AvailableStates
// - rendering-error-expected
// - rendering-error
// - clsi-maintenance
// - clsi-unavailable
// - too-recently-compiled
// - terminated
// - rate-limited
// - compile-in-progress
// - autocompile-disabled
// - project-too-large
// - timedout
// - failure
// - clear-cache
// - pdf-viewer-loading-error
// - validation-problems
function PdfErrorState() {
const { loadingError } = usePdfPreviewContext()
// TODO ide-redesign-cleanup: rename showLogs to something else and check usages
const { hasShortCompileTimeout, error, showLogs } = useCompileContext()
const newEditor = useIsNewEditorEnabled()
const { t } = useTranslation()
if (!newEditor || (!loadingError && !showLogs)) {
return null
}
if (hasShortCompileTimeout && error === 'timedout') {
return <CompileTimeoutErrorState />
switch (error) {
case 'timedout': {
if (hasShortCompileTimeout) {
return <CompileTimeoutErrorState />
} else {
return <LongCompileTimeoutErrorState />
}
}
case 'compile-in-progress':
return (
<ErrorState
title={t('pdf_compile_in_progress_error')}
description={t('pdf_compile_try_again')}
iconType="warning"
/>
)
default:
return <GeneralErrorState />
}
return <GeneralErrorState />
}
const GeneralErrorState = () => {
@@ -34,7 +67,6 @@ const GeneralErrorState = () => {
title={t('pdf_couldnt_compile')}
description={t('we_are_unable_to_generate_the_pdf_at_this_time')}
iconType="warning"
iconClassName="pdf-error-state-warning-icon"
actions={
<OLButton
variant="secondary"
@@ -52,11 +84,13 @@ const GeneralErrorState = () => {
<MaterialIcon type="info" unfilled />
{t('why_might_this_happen')}
</div>
<ul className="pdf-error-state-info-box-text">
<li>{t('there_is_an_unrecoverable_latex_error_check_logs')}</li>
<li>{t('the_document_environment_contains_no_content')}</li>
<li>{t('this_project_contains_a_file_called_output')}</li>
</ul>
<div className="pdf-error-state-info-box-text">
<ul className="pdf-error-state-info-box-list">
<li>{t('there_is_an_unrecoverable_latex_error_check_logs')}</li>
<li>{t('the_document_environment_contains_no_content')}</li>
<li>{t('this_project_contains_a_file_called_output')}</li>
</ul>
</div>
</div>
}
/>
@@ -84,6 +118,85 @@ const CompileTimeoutErrorState = () => {
)
}
const LongCompileTimeoutErrorState = () => {
const { t } = useTranslation()
const { enableStopOnFirstError } = useStopOnFirstError({
eventSource: 'timeout',
})
const { startCompile, lastCompileOptions, setAnimateCompileDropdownArrow } =
useCompileContext()
const handleEnableStopOnFirstErrorClick = useCallback(() => {
enableStopOnFirstError()
startCompile({ stopOnFirstError: true })
setAnimateCompileDropdownArrow(true)
}, [enableStopOnFirstError, startCompile, setAnimateCompileDropdownArrow])
return (
<ErrorState
title={t('compile_limit_reached')}
description={t('project_timed_out_intro_short')}
iconType="running_with_errors"
extraContent={
<div className="pdf-error-state-info-box">
<div className="pdf-error-state-info-box-title">
<MaterialIcon type="info" unfilled />
{t('project_timed_out_common_causes')}
</div>
<div className="pdf-error-state-info-box-text">
<ul className="pdf-error-state-info-box-list">
<li>
<Trans
i18nKey="project_timed_out_optimize_images"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Optimising_very_large_image_files" />,
]}
/>
</li>
<li>
<Trans
i18nKey="project_timed_out_fatal_error"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F#Fatal_compile_errors_blocking_the_compilation" />,
]}
/>
{!lastCompileOptions.stopOnFirstError && (
<>
{' '}
<Trans
i18nKey="project_timed_out_enable_stop_on_first_error"
components={[
// eslint-disable-next-line react/jsx-key
<OLButton
variant="primary"
size="sm"
onClick={handleEnableStopOnFirstErrorClick}
/>,
]}
/>
</>
)}
</li>
</ul>
<p className="mb-0">
<Trans
i18nKey="project_timed_out_learn_more"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F" />,
]}
/>
</p>
</div>
</div>
}
/>
)
}
const ErrorState = ({
title,
description,
@@ -95,14 +208,20 @@ const ErrorState = ({
title: string
description: string
iconType: string
actions: React.ReactNode
actions?: React.ReactNode
iconClassName?: string
extraContent?: React.ReactNode
}) => {
return (
<div className="pdf-error-state">
<div className="pdf-error-state-top-section">
<div className={classNames('pdf-error-state-icon', iconClassName)}>
<div
className={classNames(
'pdf-error-state-icon',
'pdf-error-state-warning-icon',
iconClassName
)}
>
<MaterialIcon type={iconType} />
</div>
<div className="pdf-error-state-text">