mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-27 02:51:57 +02:00
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:
committed by
Copybot
parent
9b601774fc
commit
e27da3bfd8
@@ -5,6 +5,13 @@ import { SplitTestContext } from '../../frontend/js/shared/context/split-test-co
|
||||
export const splitTestsArgTypes = {
|
||||
// to be able to use this utility, you need to add the argTypes for each split test in this object
|
||||
// Check the original implementation for an example: https://github.com/overleaf/internal/pull/17809
|
||||
'editor-redesign': {
|
||||
description: 'Enable the new editor redesign',
|
||||
control: {
|
||||
type: 'select' as const,
|
||||
},
|
||||
options: ['enabled'],
|
||||
},
|
||||
}
|
||||
|
||||
export const withSplitTests = (
|
||||
|
||||
@@ -1342,9 +1342,11 @@
|
||||
"project_search_result_count": "",
|
||||
"project_search_result_count_plural": "",
|
||||
"project_synchronisation": "",
|
||||
"project_timed_out_common_causes": "",
|
||||
"project_timed_out_enable_stop_on_first_error": "",
|
||||
"project_timed_out_fatal_error": "",
|
||||
"project_timed_out_intro": "",
|
||||
"project_timed_out_intro_short": "",
|
||||
"project_timed_out_learn_more": "",
|
||||
"project_timed_out_optimize_images": "",
|
||||
"project_title_options": "",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -21,7 +21,7 @@ import { ReactContextRoot } from '@/features/ide-react/context/react-context-roo
|
||||
|
||||
const scopeWatchers: [string, (value: any) => void][] = []
|
||||
|
||||
const user: User = {
|
||||
export const user: User = {
|
||||
id: 'story-user' as UserId,
|
||||
email: 'story-user@example.com',
|
||||
allowedFreeTrial: true,
|
||||
|
||||
90
services/web/frontend/stories/pdf-error-state.stories.tsx
Normal file
90
services/web/frontend/stories/pdf-error-state.stories.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
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 { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
||||
import { useEffect } from 'react'
|
||||
import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
|
||||
|
||||
export default {
|
||||
title: 'Editor / PDF Error States',
|
||||
component: PdfErrorState,
|
||||
}
|
||||
|
||||
const compileErrors = [
|
||||
'autocompile-backoff',
|
||||
'clear-cache',
|
||||
'clsi-maintenance',
|
||||
'compile-in-progress',
|
||||
'exited',
|
||||
'failure',
|
||||
'generic',
|
||||
'project-too-large',
|
||||
'rate-limited',
|
||||
'success',
|
||||
'terminated',
|
||||
'timedout',
|
||||
'too-recently-compiled',
|
||||
'unavailable',
|
||||
'validation-problems',
|
||||
'foo',
|
||||
] as const
|
||||
|
||||
const ErrorPane = ({ error }: { error: (typeof compileErrors)[number] }) => {
|
||||
window.metaAttributesCache.set('ol-splitTestVariants', {
|
||||
'editor-redesign': 'enabled',
|
||||
})
|
||||
useFetchMock(fetchMock => {
|
||||
mockCompile(fetchMock)
|
||||
})
|
||||
const { setError } = useCompileContext()
|
||||
useEffect(() => {
|
||||
setError(error)
|
||||
}, [setError, error])
|
||||
|
||||
return (
|
||||
<div className="ide-redesign-main">
|
||||
<span style={{ fontFamily: 'monospace' }}>{error}</span>
|
||||
<PdfPreview />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const story = {
|
||||
render: (args: { error: (typeof compileErrors)[number] }) => {
|
||||
return <ErrorPane error={args.error} />
|
||||
},
|
||||
argTypes: {
|
||||
error: {
|
||||
options: compileErrors,
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
error: compileErrors[0],
|
||||
},
|
||||
}
|
||||
|
||||
export const PremiumUser = {
|
||||
...story,
|
||||
decorators: [ScopeDecorator],
|
||||
}
|
||||
|
||||
export const FreeUser = {
|
||||
...story,
|
||||
decorators: [
|
||||
(Story: any) =>
|
||||
ScopeDecorator(
|
||||
Story,
|
||||
{ mockCompileOnLoad: false },
|
||||
{
|
||||
'ol-user': {
|
||||
...user,
|
||||
features: { ...user.features, compileTimeout: 20 },
|
||||
},
|
||||
}
|
||||
),
|
||||
],
|
||||
}
|
||||
@@ -156,6 +156,12 @@
|
||||
--link-visited-color: var(--link-visited-color-dark);
|
||||
}
|
||||
|
||||
@mixin themed-links {
|
||||
--link-color: var(--link-ui-themed);
|
||||
--link-hover-color: var(--link-ui-hover-themed);
|
||||
--link-visited-color: var(--link-ui-visited-themed);
|
||||
}
|
||||
|
||||
@mixin triangle($direction, $width, $height, $color) {
|
||||
position: absolute;
|
||||
border-color: transparent;
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
:root {
|
||||
--pdf-error-state-info-box-color: var(--content-primary-dark);
|
||||
--pdf-error-state-info-box-background: var(--bg-dark-primary);
|
||||
--pdf-error-state-info-box-border: var(--border-divider-dark);
|
||||
--pdf-error-state-label-color: var(--content-primary-dark);
|
||||
--pdf-error-state-description-color: var(--content-secondary-dark);
|
||||
}
|
||||
|
||||
@include theme('light') {
|
||||
--pdf-error-state-info-box-color: var(--content-primary);
|
||||
--pdf-error-state-info-box-background: var(--bg-light-primary);
|
||||
--pdf-error-state-info-box-border: var(--border-divider);
|
||||
--pdf-error-state-label-color: var(--content-primary);
|
||||
--pdf-error-state-description-color: var(--content-secondary);
|
||||
}
|
||||
|
||||
.pdf-error-state {
|
||||
position: absolute;
|
||||
inset: var(--toolbar-small-height) 0 0 0;
|
||||
@@ -34,6 +18,7 @@
|
||||
}
|
||||
|
||||
.pdf-error-state-icon {
|
||||
color: var(--content-primary-themed);
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
@@ -44,14 +29,14 @@
|
||||
.material-symbols {
|
||||
font-size: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-error-state-warning-icon {
|
||||
background-color: var(--bg-danger-03);
|
||||
color: var(--content-danger);
|
||||
&.pdf-error-state-warning-icon {
|
||||
background-color: var(--bg-danger-03);
|
||||
color: var(--content-danger);
|
||||
|
||||
.material-symbols {
|
||||
font-size: 32px;
|
||||
.material-symbols {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,23 +49,25 @@
|
||||
|
||||
.pdf-error-state-label {
|
||||
font-size: var(--font-size-02);
|
||||
color: var(--pdf-error-state-label-color);
|
||||
color: var(--content-primary-themed);
|
||||
font-weight: 600;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pdf-error-state-description {
|
||||
color: var(--pdf-error-state-description-color);
|
||||
color: var(--content-secondary-themed);
|
||||
font-size: var(--font-size-02);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pdf-error-state-info-box {
|
||||
background-color: var(--pdf-error-state-info-box-background);
|
||||
color: var(--pdf-error-state-info-box-color);
|
||||
background-color: var(--bg-primary-themed);
|
||||
color: var(--content-primary-themed);
|
||||
padding: var(--spacing-06);
|
||||
border: 1px solid var(--pdf-error-state-info-box-border);
|
||||
border: 1px solid var(--border-divider-themed);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
@include themed-links;
|
||||
}
|
||||
|
||||
.pdf-error-state-info-box-title {
|
||||
@@ -94,8 +81,12 @@
|
||||
|
||||
.pdf-error-state-info-box-text {
|
||||
font-size: var(--font-size-02);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-02);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pdf-error-state-info-box-list {
|
||||
margin-bottom: 0;
|
||||
|
||||
li + li {
|
||||
margin-top: var(--spacing-02);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1753,9 +1753,11 @@
|
||||
"project_search_result_count": "__count__ result",
|
||||
"project_search_result_count_plural": "__count__ results",
|
||||
"project_synchronisation": "Project synchronisation",
|
||||
"project_timed_out_common_causes": "Most common causes of timeouts are:",
|
||||
"project_timed_out_enable_stop_on_first_error": "<0>Enable “Stop on first error”</0> to help you find and fix errors right away.",
|
||||
"project_timed_out_fatal_error": "A <0>fatal compile error</0> may be completely blocking compilation.",
|
||||
"project_timed_out_intro": "Sorry, your compile took too long to run and timed out. The most common causes of timeouts are:",
|
||||
"project_timed_out_intro_short": "Sorry, your compile took too long to run and timed out",
|
||||
"project_timed_out_learn_more": "<0>Learn more</0> about other causes of compile timeouts and how to fix them.",
|
||||
"project_timed_out_optimize_images": "Large or high-resolution images are taking too long to process. You may be able to <0>optimize them</0>.",
|
||||
"project_too_large": "Project too large",
|
||||
|
||||
Reference in New Issue
Block a user