mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
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:
parent
94773e898e
commit
03e1daa038
14 changed files with 197 additions and 17 deletions
|
@ -712,6 +712,7 @@ module.exports = {
|
|||
publishModal: [],
|
||||
tprLinkedFileInfo: [],
|
||||
tprLinkedFileRefreshError: [],
|
||||
contactUsModal: [],
|
||||
},
|
||||
|
||||
moduleImportSequence: ['launchpad', 'server-ce-scripts', 'user-activate'],
|
||||
|
|
|
@ -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": "",
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
|
@ -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" />
|
||||
))
|
||||
|
|
|
@ -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" />
|
||||
))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" />
|
||||
))
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
35
services/web/frontend/stories/contact-us-modal.stories.js
Normal file
35
services/web/frontend/stories/contact-us-modal.stories.js
Normal 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' },
|
||||
},
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in a new issue