Merge pull request #28037 from overleaf/dp-additional-error-states

Add additional pdf error states in new editor

GitOrigin-RevId: a7e08652cb5ff23d2139106629f13aad20bd32a0
This commit is contained in:
David
2025-08-22 08:05:22 +00:00
committed by Copybot
parent 6190005f9b
commit 76e23434ed
6 changed files with 237 additions and 134 deletions
@@ -1,103 +1,13 @@
import OLButton from '@/shared/components/ol/ol-button'
import MaterialIcon from '@/shared/components/material-icon'
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'
import ErrorState from './error-state'
// 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
}
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 />
}
}
const GeneralErrorState = () => {
const { t } = useTranslation()
const { openTab: openRailTab } = useRailContext()
return (
<ErrorState
title={t('pdf_couldnt_compile')}
description={t('we_are_unable_to_generate_the_pdf_at_this_time')}
iconType="warning"
actions={
<OLButton
variant="secondary"
size="sm"
onClick={() => {
openRailTab('errors')
}}
>
{t('check_logs')}
</OLButton>
}
extraContent={
<div className="pdf-error-state-info-box">
<div className="pdf-error-state-info-box-title">
<MaterialIcon type="info" unfilled />
{t('why_might_this_happen')}
</div>
<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>
}
/>
)
}
const CompileTimeoutErrorState = () => {
export const ShortCompileTimeoutErrorState = () => {
const { t } = useTranslation()
return (
@@ -118,7 +28,7 @@ const CompileTimeoutErrorState = () => {
)
}
const LongCompileTimeoutErrorState = () => {
export const LongCompileTimeoutErrorState = () => {
const { t } = useTranslation()
const { enableStopOnFirstError } = useStopOnFirstError({
@@ -196,42 +106,3 @@ const LongCompileTimeoutErrorState = () => {
/>
)
}
const ErrorState = ({
title,
description,
iconType,
actions,
iconClassName,
extraContent,
}: {
title: string
description: string
iconType: string
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',
'pdf-error-state-warning-icon',
iconClassName
)}
>
<MaterialIcon type={iconType} />
</div>
<div className="pdf-error-state-text">
<p className="pdf-error-state-label">{title}</p>
<p className="pdf-error-state-description">{description}</p>
</div>
{actions}
</div>
{extraContent}
</div>
)
}
export default PdfErrorState
@@ -0,0 +1,60 @@
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
import MaterialIcon from '@/shared/components/material-icon'
import OLButton from '@/shared/components/ol/ol-button'
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
export default function ErrorState({
title,
description,
iconType = 'warning',
actions,
iconClassName,
extraContent,
}: {
title: string
description: React.ReactNode
iconType?: string
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',
'pdf-error-state-warning-icon',
iconClassName
)}
>
<MaterialIcon type={iconType} />
</div>
<div className="pdf-error-state-text">
<p className="pdf-error-state-label">{title}</p>
<p className="pdf-error-state-description">{description}</p>
</div>
{actions}
</div>
{extraContent}
</div>
)
}
export const CheckLogsButton = () => {
const { t } = useTranslation()
const { openTab: openRailTab } = useRailContext()
return (
<OLButton
variant="secondary"
size="sm"
onClick={() => {
openRailTab('errors')
}}
>
{t('check_logs')}
</OLButton>
)
}
@@ -0,0 +1,30 @@
import { useTranslation } from 'react-i18next'
import ErrorState, { CheckLogsButton } from './error-state'
import MaterialIcon from '@/shared/components/material-icon'
export default function GeneralErrorState() {
const { t } = useTranslation()
return (
<ErrorState
title={t('pdf_couldnt_compile')}
description={t('we_are_unable_to_generate_the_pdf_at_this_time')}
actions={<CheckLogsButton />}
extraContent={
<div className="pdf-error-state-info-box">
<div className="pdf-error-state-info-box-title">
<MaterialIcon type="info" unfilled />
{t('why_might_this_happen')}
</div>
<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>
}
/>
)
}
@@ -0,0 +1,139 @@
import { Trans, useTranslation } from 'react-i18next'
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 ErrorState, { CheckLogsButton } from './error-state'
import {
LongCompileTimeoutErrorState,
ShortCompileTimeoutErrorState,
} from './compile-timeout-error-state'
import GeneralErrorState from './general-error-state'
// 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
}
switch (error) {
case 'timedout': {
if (hasShortCompileTimeout) {
return <ShortCompileTimeoutErrorState />
} else {
return <LongCompileTimeoutErrorState />
}
}
case 'compile-in-progress':
return (
<ErrorState
title={t('pdf_compile_in_progress_error')}
description={t('pdf_compile_try_again')}
/>
)
case 'clsi-maintenance':
return (
<ErrorState
title={t('server_error')}
description={t('clsi_maintenance')}
iconType="build"
/>
)
case 'clsi-unavailable':
return (
<ErrorState
title={t('server_error')}
description={t('clsi_unavailable')}
/>
)
case 'too-recently-compiled':
return (
<ErrorState
title={t('server_error')}
description={t('too_recently_compiled')}
/>
)
case 'terminated':
return (
<ErrorState
title={t('terminated')}
description={t('compile_terminated_by_user')}
actions={<CheckLogsButton />}
/>
)
case 'rate-limited':
return (
<ErrorState
title={t('pdf_compile_rate_limit_hit')}
description={t('project_flagged_too_many_compiles')}
/>
)
case 'autocompile-disabled':
return (
<ErrorState
title={t('autocompile_disabled')}
description={t('autocompile_disabled_reason')}
/>
)
case 'project-too-large':
return (
<ErrorState
title={t('project_too_large')}
description={t('project_too_much_editable_text')}
/>
)
case 'clear-cache':
return (
<ErrorState
title={t('server_error')}
description={t('somthing_went_wrong_compiling')}
/>
)
case 'pdf-viewer-loading-error':
return (
<ErrorState
title={t('pdf_rendering_error')}
description={
<Trans
i18nKey="something_went_wrong_loading_pdf_viewer"
components={[
<strong key="strong-" />,
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href="/learn/how-to/Resolving_access%2C_loading%2C_and_display_problems"
target="_blank"
key="troubleshooting-link"
/>,
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a key="contact-link" target="_blank" href="/contact" />,
]}
/>
}
/>
)
default:
return <GeneralErrorState />
}
}
export default PdfErrorState
@@ -9,7 +9,7 @@ import { PdfPreviewMessages } from './pdf-preview-messages'
import CompileTimeWarningUpgradePrompt from './compile-time-warning-upgrade-prompt'
import { PdfPreviewProvider } from './pdf-preview-provider'
import PdfPreviewHybridToolbarNew from '@/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar'
import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-error-state'
import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-error-state/pdf-error-state'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import PdfCodeCheckFailedBanner from '@/features/ide-redesign/components/pdf-preview/pdf-code-check-failed-banner'
@@ -1,7 +1,7 @@
import useFetchMock from './hooks/use-fetch-mock'
import { mockCompile } from './fixtures/compile'
import { ScopeDecorator, user } from './decorators/scope'
import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-error-state'
import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-error-state/pdf-error-state'
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
import { useEffect } from 'react'
import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
@@ -12,13 +12,16 @@ export default {
}
const compileErrors = [
'autocompile-disabled',
'autocompile-backoff',
'clear-cache',
'clsi-maintenance',
'clsi-unavailable',
'compile-in-progress',
'exited',
'failure',
'generic',
'pdf-viewer-loading-error',
'project-too-large',
'rate-limited',
'success',