Merge pull request #5201 from overleaf/msm-pdf-viewer-error-boundaries

Error boundaries for React PDF viewer

GitOrigin-RevId: 90052fc183f7ece8125ecfb0410a529cf905c13b
This commit is contained in:
June Kelly 2021-10-06 09:33:24 +01:00 committed by Copybot
parent 94773e898e
commit 03e1daa038
14 changed files with 197 additions and 17 deletions

View file

@ -712,6 +712,7 @@ module.exports = {
publishModal: [],
tprLinkedFileInfo: [],
tprLinkedFileRefreshError: [],
contactUsModal: [],
},
moduleImportSequence: ['launchpad', 'server-ce-scripts', 'user-activate'],

View file

@ -54,7 +54,9 @@
"compiling": "",
"conflicting_paths_found": "",
"connected_users": "",
"contact_message_label": "",
"continue_github_merge": "",
"contact_us": "",
"copy": "",
"copy_project": "",
"copying": "",
@ -179,6 +181,7 @@
"log_hint_extra_info": "",
"logs_pane_info_message": "",
"logs_pane_info_message_popup": "",
"log_viewer_error": "",
"main_file_not_found": "",
"make_private": "",
"manage_files_from_your_dropbox_folder": "",
@ -231,7 +234,9 @@
"pdf_compile_in_progress_error": "",
"pdf_compile_rate_limit_hit": "",
"pdf_compile_try_again": "",
"pdf_preview_error": "",
"pdf_rendering_error": "",
"pdf_viewer_error": "",
"please_compile_pdf_before_download": "",
"please_refresh": "",
"please_select_a_file": "",
@ -252,6 +257,7 @@
"project_too_large": "",
"project_too_large_please_reduce": "",
"project_too_much_editable_text": "",
"project_url" : "",
"public": "",
"pull_github_changes_into_sharelatex": "",
"push_sharelatex_changes_to_github": "",
@ -307,6 +313,7 @@
"stop_compile": "",
"stop_on_validation_error": "",
"store_your_work": "",
"subject": "",
"submit_title": "",
"sure_you_want_to_delete": "",
"sync_project_to_github_explanation": "",
@ -328,6 +335,8 @@
"too_recently_compiled": "",
"total_words": "",
"try_it_for_free": "",
"try_recompile_project": "",
"try_refresh_page": "",
"turn_off_link_sharing": "",
"turn_on_link_sharing": "",
"unlimited_projects": "",

View file

@ -12,6 +12,7 @@ import { useUserContext } from '../../../shared/context/user-context'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import { FetchError } from '../../../infrastructure/fetch-json'
import { useChatContext } from '../context/chat-context'
import LoadingSpinner from '../../../shared/components/loading-spinner'
const ChatPane = React.memo(function ChatPane() {
const { t } = useTranslation()
@ -87,16 +88,6 @@ const ChatPane = React.memo(function ChatPane() {
)
})
function LoadingSpinner() {
const { t } = useTranslation()
return (
<div className="loading">
<Icon type="fw" modifier="refresh" spin />
{` ${t('loading')}`}
</div>
)
}
function Placeholder() {
const { t } = useTranslation()
return (

View file

@ -0,0 +1,88 @@
import PropTypes from 'prop-types'
import { Alert } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useState } from 'react'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
const [contactUsModalModules] = importOverleafModules('contactUsModal')
const ContactUsModal = contactUsModalModules?.import.default
function ErrorBoundaryFallback({ type }) {
const { t } = useTranslation()
const [showContactUsModal, setShowContactUsModal] = useState(false)
function handleContactUsClick() {
setShowContactUsModal(true)
}
function handleContactUsModalHide() {
setShowContactUsModal(false)
}
if (!ContactUsModal) {
return (
<div className="pdf-error-alert">
<Alert bsStyle="danger">
{`${t('generic_something_went_wrong')}. ${t('please_refresh')}`}
</Alert>
</div>
)
}
// we create each instance of `<Trans/>` individually so `i18next-scanner` can detect hardcoded `i18nKey` values
let content
if (type === 'pdf') {
content = (
<>
<p>{t('pdf_viewer_error')}</p>
<p>
<Trans
i18nKey="try_recompile_project"
components={[<a href="#" onClick={handleContactUsClick} />]} // eslint-disable-line react/jsx-key, jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid
/>
</p>
</>
)
} else if (type === 'logs') {
content = (
<>
<p>{t('log_viewer_error')}</p>
<p>
<Trans
i18nKey="try_recompile_project"
components={[<a href="#" onClick={handleContactUsClick} />]} // eslint-disable-line react/jsx-key, jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid
/>
</p>
</>
)
} else {
content = (
<>
<p>{t('pdf_preview_error')}</p>
<p>
<Trans
i18nKey="try_refresh_page"
components={[<a href="#" onClick={handleContactUsClick} />]} // eslint-disable-line react/jsx-key, jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid
/>
</p>
</>
)
}
return (
<div className="pdf-error-alert">
<Alert bsStyle="danger">{content}</Alert>
<ContactUsModal
show={showContactUsModal}
handleHide={handleContactUsModalHide}
/>
</div>
)
}
ErrorBoundaryFallback.propTypes = {
type: PropTypes.oneOf(['preview', 'pdf', 'logs']).isRequired,
}
export default ErrorBoundaryFallback

View file

@ -8,6 +8,8 @@ import usePersistedState from '../../../shared/hooks/use-persisted-state'
import useScopeValue from '../../../shared/context/util/scope-value-hook'
import { buildHighlightElement } from '../util/highlights'
import PDFJSWrapper from '../util/pdf-js-wrapper'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import ErrorBoundaryFallback from './error-boundary-fallback'
function PdfJsViewer({ url }) {
const { _id: projectId } = useProjectContext()
@ -248,4 +250,6 @@ PdfJsViewer.propTypes = {
url: PropTypes.string.isRequired,
}
export default memo(PdfJsViewer)
export default withErrorBoundary(memo(PdfJsViewer), () => (
<ErrorBoundaryFallback type="pdf" />
))

View file

@ -9,6 +9,8 @@ import PdfPreviewError from './pdf-preview-error'
import PdfClearCacheButton from './pdf-clear-cache-button'
import PdfDownloadFilesButton from './pdf-download-files-button'
import PdfLogsEntries from './pdf-logs-entries'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import ErrorBoundaryFallback from './error-boundary-fallback'
function PdfLogsViewer() {
const {
@ -67,4 +69,6 @@ function PdfLogsViewer() {
)
}
export default memo(PdfLogsViewer)
export default withErrorBoundary(memo(PdfLogsViewer), () => (
<ErrorBoundaryFallback type="logs" />
))

View file

@ -2,8 +2,8 @@ import { memo, Suspense } from 'react'
import PdfLogsViewer from './pdf-logs-viewer'
import PdfViewer from './pdf-viewer'
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import PdfPreviewToolbar from './pdf-preview-toolbar'
import LoadingSpinner from '../../../shared/components/loading-spinner'
function PdfPreviewPane() {
const { showLogs } = usePdfPreviewContext()
@ -11,7 +11,7 @@ function PdfPreviewPane() {
return (
<div className="pdf full-size">
<PdfPreviewToolbar />
<Suspense fallback={<div>Loading</div>}>
<Suspense fallback={<LoadingPreview />}>
<div className="pdf-viewer">
<PdfViewer />
</div>
@ -21,4 +21,12 @@ function PdfPreviewPane() {
)
}
export default memo(withErrorBoundary(PdfPreviewPane))
function LoadingPreview() {
return (
<div className="pdf-loading-spinner-container">
<LoadingSpinner />
</div>
)
}
export default memo(PdfPreviewPane)

View file

@ -1,6 +1,8 @@
import PdfPreviewProvider from '../contexts/pdf-preview-context'
import PdfPreviewPane from './pdf-preview-pane'
import { memo } from 'react'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import ErrorBoundaryFallback from './error-boundary-fallback'
function PdfPreview() {
return (
@ -10,4 +12,6 @@ function PdfPreview() {
)
}
export default memo(PdfPreview)
export default withErrorBoundary(memo(PdfPreview), () => (
<ErrorBoundaryFallback type="preview" />
))

View file

@ -0,0 +1,14 @@
import { useTranslation } from 'react-i18next'
import Icon from './icon'
function LoadingSpinner() {
const { t } = useTranslation()
return (
<div className="loading">
<Icon type="fw" modifier="refresh" spin />
{` ${t('loading')}`}
</div>
)
}
export default LoadingSpinner

View file

@ -8,6 +8,7 @@ UserContext.Provider.propTypes = {
value: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string,
email: PropTypes.string,
allowedFreeTrial: PropTypes.boolean,
first_name: PropTypes.string,
last_name: PropTypes.string,

View file

@ -0,0 +1,35 @@
import { useState } from 'react'
import useFetchMock from './hooks/use-fetch-mock'
import ContactUsModal from '../../modules/support/frontend/js/components/contact-us-modal'
import { withContextRoot } from './utils/with-context-root'
export const Generic = () => {
const [show, setShow] = useState(true)
const handleHide = () => setShow(false)
useFetchMock(fetchMock => {
fetchMock.post('express:/support', { status: 200 }, { delay: 1000 })
})
return withContextRoot(<ContactUsModal show={show} handleHide={handleHide} />)
}
export const RequestError = args => {
useFetchMock(fetchMock => {
fetchMock.post('express:/support', { status: 404 }, { delay: 250 })
})
return withContextRoot(<ContactUsModal {...args} />)
}
export default {
title: 'Modals / Contact Us',
component: ContactUsModal,
args: {
show: true,
handleHide: () => {},
},
argTypes: {
handleHide: { action: 'close modal' },
},
}

View file

@ -538,3 +538,19 @@
color: #fff;
}
}
.pdf-error-alert {
position: absolute;
width: 100%;
height: 100%;
background-color: @pdf-bg;
padding: @line-height-computed / 2;
}
.pdf-loading-spinner-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}

View file

@ -701,6 +701,11 @@
"open_a_file_on_the_left": "Open a file on the left",
"reference_error_relink_hint": "If this error persists, try re-linking your account here:",
"pdf_rendering_error": "PDF Rendering Error",
"pdf_preview_error": "There was a problem displaying the compilation results for this project. This is an internal __appName__ issue, not a problem with your LaTeX code.",
"pdf_viewer_error": "There was a problem displaying this project PDF. This is an internal __appName__ issue, not a problem with your LaTeX code.",
"log_viewer_error": "There was a problem displaying this project compilation errors and logs. This is an internal __appName__ issue, not a problem with your LaTeX code.",
"try_recompile_project": "Please try recompiling the project. If the problem persists, <0>contact us</0>",
"try_refresh_page": "Please try refreshing this page. If the problem persists, <0>contact us</0>.",
"something_went_wrong_rendering_pdf": "Something went wrong while rendering this PDF.",
"mendeley_reference_loading_error_expired": "Mendeley token expired, please re-link your account",
"zotero_reference_loading_error_expired": "Zotero token expired, please re-link your account",

View file

@ -14,7 +14,7 @@ import { SplitTestProvider } from '../../../frontend/js/shared/context/split-tes
import { CompileProvider } from '../../../frontend/js/shared/context/compile-context'
export function EditorProviders({
user = { id: '123abd' },
user = { id: '123abd', email: 'testuser@example.com' },
projectId = 'project123',
socket = {
on: sinon.stub(),