diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.js b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.js new file mode 100644 index 0000000000..f4bfdb2207 --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.js @@ -0,0 +1,37 @@ +import { useTranslation } from 'react-i18next' +import PdfLogEntryRawContent from './pdf-log-entry-raw-content' +import PropTypes from 'prop-types' + +export default function PdfLogEntryContent({ + rawContent, + formattedContent, + extraInfoURL, +}) { + const { t } = useTranslation() + + return ( +
+ {formattedContent && ( +
{formattedContent}
+ )} + + {extraInfoURL && ( +
+ + {t('log_hint_extra_info')} + +
+ )} + + {rawContent && ( + + )} +
+ ) +} + +PdfLogEntryContent.propTypes = { + rawContent: PropTypes.string, + formattedContent: PropTypes.node, + extraInfoURL: PropTypes.string, +} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.js b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.js new file mode 100644 index 0000000000..326dd614cf --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.js @@ -0,0 +1,66 @@ +import { useState } from 'react' +import { useResizeObserver } from '../hooks/use-resize-observer' +import { useTranslation } from 'react-i18next' +import classNames from 'classnames' +import { Button } from 'react-bootstrap' +import Icon from '../../../shared/components/icon' +import PropTypes from 'prop-types' + +export default function PdfLogEntryRawContent({ + rawContent, + collapsedSize = 0, +}) { + const [expanded, setExpanded] = useState(false) + const [needsExpander, setNeedsExpander] = useState(false) + + const containerRef = useResizeObserver(entry => { + setNeedsExpander(entry.target.scrollHeight > collapsedSize) + }) + + const { t } = useTranslation() + + return ( +
+
+
+          {rawContent.trim()}
+        
+
+ + {needsExpander && ( +
+ +
+ )} +
+ ) +} + +PdfLogEntryRawContent.propTypes = { + rawContent: PropTypes.string.isRequired, + collapsedSize: PropTypes.number, +} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js new file mode 100644 index 0000000000..b70c891a5d --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js @@ -0,0 +1,76 @@ +import PropTypes from 'prop-types' +import classNames from 'classnames' +import { memo, useCallback } from 'react' +import PreviewLogEntryHeader from '../../preview/components/preview-log-entry-header' +import PdfLogEntryContent from './pdf-log-entry-content' + +function PdfLogEntry({ + headerTitle, + headerIcon, + rawContent, + logType, + formattedContent, + extraInfoURL, + level, + sourceLocation, + showSourceLocationLink = true, + showCloseButton = false, + entryAriaLabel = null, + customClass, + onSourceLocationClick, + onClose, +}) { + const handleLogEntryLinkClick = useCallback( + event => { + event.preventDefault() + onSourceLocationClick(sourceLocation) + }, + [onSourceLocationClick, sourceLocation] + ) + + return ( +
+ + {(rawContent || formattedContent) && ( + + )} +
+ ) +} + +PdfLogEntry.propTypes = { + sourceLocation: PreviewLogEntryHeader.propTypes.sourceLocation, + headerTitle: PropTypes.string, + headerIcon: PropTypes.element, + rawContent: PropTypes.string, + logType: PropTypes.string, + formattedContent: PropTypes.node, + extraInfoURL: PropTypes.string, + level: PropTypes.oneOf(['error', 'warning', 'typesetting', 'raw', 'success']) + .isRequired, + customClass: PropTypes.string, + showSourceLocationLink: PropTypes.bool, + showCloseButton: PropTypes.bool, + entryAriaLabel: PropTypes.string, + onSourceLocationClick: PropTypes.func, + onClose: PropTypes.func, +} + +export default memo(PdfLogEntry) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js index 1587906de2..552e431f21 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js @@ -1,8 +1,8 @@ import { memo, useCallback } from 'react' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' -import PreviewLogsPaneEntry from '../../preview/components/preview-logs-pane-entry' import PreviewLogsPaneMaxEntries from '../../preview/components/preview-logs-pane-max-entries' +import PdfLogEntry from './pdf-log-entry' const LOG_PREVIEW_LIMIT = 100 @@ -23,13 +23,12 @@ function PdfLogsEntries({ entries }) { <> {entries.length > LOG_PREVIEW_LIMIT && ( )} {logEntries.map(logEntry => ( - ', function () { // xhrMock.teardown() fetchMock.reset() localStorage.clear() + sinon.restore() }) it('renders the PDF preview', async function () { @@ -270,6 +271,35 @@ describe('', function () { expect(fetchMock.called('express:/build/:file')).to.be.false // TODO: actual path }) + it('displays expandable raw logs', async function () { + mockCompile() + mockBuildFile() + + // pretend that the content is large enough to trigger a "collapse" + // (in jsdom these values are always zero) + sinon.stub(HTMLElement.prototype, 'scrollHeight').value(500) + sinon.stub(HTMLElement.prototype, 'scrollWidth').value(500) + + renderWithEditorContext(, { scope }) + + // wait for "compile on load" to finish + await screen.findByRole('button', { name: 'Compiling…' }) + await screen.findByRole('button', { name: 'Recompile' }) + + const logsButton = screen.getByRole('button', { name: 'View logs' }) + logsButton.click() + + await screen.findByRole('button', { name: 'View PDF' }) + + // expand the log + const expandButton = screen.getByRole('button', { name: 'Expand' }) + expandButton.click() + + // collapse the log + const collapseButton = screen.getByRole('button', { name: 'Collapse' }) + collapseButton.click() + }) + it('displays error messages if there were validation problems', async function () { const validationProblems = { sizeCheck: {