mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-02 15:53:04 -05:00
15f49994bd
* Animate recompile button when autocompile is waiting * Add code-check failed notice to the new compile UI GitOrigin-RevId: 83b62f41438e8e5b94bd893c222bec37745c0f57
205 lines
5.7 KiB
JavaScript
205 lines
5.7 KiB
JavaScript
import React from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { Dropdown } from 'react-bootstrap'
|
|
import PreviewLogsPaneEntry from './preview-logs-pane-entry'
|
|
import PreviewValidationIssue from './preview-validation-issue'
|
|
import PreviewDownloadFileList from './preview-download-file-list'
|
|
import PreviewError from './preview-error'
|
|
import Icon from '../../../shared/components/icon'
|
|
import usePersistedState from '../../../infrastructure/persisted-state-hook'
|
|
|
|
function PreviewLogsPane({
|
|
logEntries = { all: [], errors: [], warnings: [], typesetting: [] },
|
|
rawLog = '',
|
|
validationIssues = {},
|
|
errors = {},
|
|
outputFiles = [],
|
|
isClearingCache,
|
|
isCompiling = false,
|
|
autoCompileHasLintingError = false,
|
|
onLogEntryLocationClick,
|
|
onClearCache
|
|
}) {
|
|
const { t } = useTranslation()
|
|
const {
|
|
all: allCompilerIssues = [],
|
|
errors: compilerErrors = [],
|
|
warnings: compilerWarnings = [],
|
|
typesetting: compilerTypesettingIssues = []
|
|
} = logEntries
|
|
|
|
const errorsUI = Object.keys(errors).map((name, index) => (
|
|
<PreviewError key={index} name={name} />
|
|
))
|
|
|
|
const validationIssuesUI = Object.keys(
|
|
validationIssues
|
|
).map((name, index) => (
|
|
<PreviewValidationIssue
|
|
key={index}
|
|
name={name}
|
|
details={validationIssues[name]}
|
|
/>
|
|
))
|
|
|
|
const logEntriesUI = [
|
|
...compilerErrors,
|
|
...compilerWarnings,
|
|
...compilerTypesettingIssues
|
|
].map((logEntry, idx) => (
|
|
<PreviewLogsPaneEntry
|
|
key={idx}
|
|
headerTitle={logEntry.message}
|
|
rawContent={logEntry.content}
|
|
logType={logEntry.type}
|
|
formattedContent={logEntry.humanReadableHintComponent}
|
|
extraInfoURL={logEntry.extraInfoURL}
|
|
level={logEntry.level}
|
|
entryAriaLabel={t('log_entry_description', {
|
|
level: logEntry.level
|
|
})}
|
|
sourceLocation={{
|
|
file: logEntry.file,
|
|
line: logEntry.line,
|
|
column: logEntry.column
|
|
}}
|
|
onSourceLocationClick={onLogEntryLocationClick}
|
|
/>
|
|
))
|
|
|
|
const actionsUI = (
|
|
<div className="logs-pane-actions">
|
|
<button
|
|
className="btn btn-sm btn-danger logs-pane-actions-clear-cache"
|
|
onClick={onClearCache}
|
|
disabled={isClearingCache || isCompiling}
|
|
>
|
|
{isClearingCache ? (
|
|
<Icon type="refresh" spin />
|
|
) : (
|
|
<Icon type="trash-o" />
|
|
)}
|
|
|
|
<span>{t('clear_cached_files')}</span>
|
|
</button>
|
|
<Dropdown
|
|
id="dropdown-files-logs-pane"
|
|
dropup
|
|
pullRight
|
|
disabled={isCompiling}
|
|
>
|
|
<Dropdown.Toggle
|
|
className="btn btn-sm btn-info dropdown-toggle"
|
|
title={t('other_logs_and_files')}
|
|
bsStyle="info"
|
|
/>
|
|
<Dropdown.Menu id="dropdown-files-logs-pane-list">
|
|
<PreviewDownloadFileList fileList={outputFiles} />
|
|
</Dropdown.Menu>
|
|
</Dropdown>
|
|
</div>
|
|
)
|
|
|
|
const rawLogUI = (
|
|
<PreviewLogsPaneEntry
|
|
headerTitle={t('raw_logs')}
|
|
rawContent={rawLog}
|
|
entryAriaLabel={t('raw_logs_description')}
|
|
level="raw"
|
|
/>
|
|
)
|
|
|
|
return (
|
|
<div className="logs-pane">
|
|
<div className="logs-pane-content">
|
|
<LogsPaneBetaNotice />
|
|
{autoCompileHasLintingError ? <AutoCompileLintingErrorEntry /> : null}
|
|
{errors ? errorsUI : null}
|
|
{validationIssues ? validationIssuesUI : null}
|
|
{allCompilerIssues.length > 0 ? logEntriesUI : null}
|
|
{rawLog && rawLog !== '' ? rawLogUI : null}
|
|
{actionsUI}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function AutoCompileLintingErrorEntry() {
|
|
const { t } = useTranslation()
|
|
return (
|
|
<div className="log-entry">
|
|
<div className="log-entry-header log-entry-header-error">
|
|
<div className="log-entry-header-icon-container">
|
|
<Icon type="exclamation-triangle" modifier="fw" />
|
|
</div>
|
|
<h3 className="log-entry-header-title">
|
|
{t('code_check_failed_explanation')}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LogsPaneBetaNotice() {
|
|
const { t } = useTranslation()
|
|
const [dismissedBetaNotice, setDismissedBetaNotice] = usePersistedState(
|
|
`logs_pane.dismissed_beta_notice`,
|
|
false
|
|
)
|
|
|
|
function handleDismissButtonClick() {
|
|
setDismissedBetaNotice(true)
|
|
}
|
|
|
|
return dismissedBetaNotice ? null : (
|
|
<div className="log-entry">
|
|
<div className="log-entry-header log-entry-header-raw">
|
|
<div className="log-entry-header-icon-container">
|
|
<span className="beta-badge" />
|
|
</div>
|
|
<h3 className="log-entry-header-title">
|
|
{t('logs_pane_beta_message')}
|
|
</h3>
|
|
<a
|
|
href="/beta/participate"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="log-entry-header-link log-entry-header-link-raw"
|
|
>
|
|
<span className="log-entry-header-link-location">
|
|
{t('give_feedback')}
|
|
</span>
|
|
</a>
|
|
<button
|
|
className="btn-inline-link log-entry-header-link"
|
|
type="button"
|
|
aria-label={t('dismiss')}
|
|
onClick={handleDismissButtonClick}
|
|
>
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
PreviewLogsPane.propTypes = {
|
|
logEntries: PropTypes.shape({
|
|
all: PropTypes.array,
|
|
errors: PropTypes.array,
|
|
warning: PropTypes.array,
|
|
typesetting: PropTypes.array
|
|
}),
|
|
autoCompileHasLintingError: PropTypes.bool,
|
|
rawLog: PropTypes.string,
|
|
outputFiles: PropTypes.array,
|
|
isClearingCache: PropTypes.bool,
|
|
isCompiling: PropTypes.bool,
|
|
onLogEntryLocationClick: PropTypes.func.isRequired,
|
|
onClearCache: PropTypes.func.isRequired,
|
|
validationIssues: PropTypes.object,
|
|
errors: PropTypes.object
|
|
}
|
|
|
|
export default PreviewLogsPane
|