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 '../../../shared/hooks/use-persisted-state' function PreviewLogsPane({ logEntries = { all: [], errors: [], warnings: [], typesetting: [] }, rawLog = '', validationIssues = {}, errors = {}, outputFiles = [], isClearingCache, isCompiling = false, autoCompileHasLintingError = false, variantWithFirstErrorPopup, 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"> <LogsPaneInfoNotice variantWithFirstErrorPopup={variantWithFirstErrorPopup} /> {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 LogsPaneInfoNotice({ variantWithFirstErrorPopup }) { const { t } = useTranslation() const [dismissedInfoNotice, setDismissedInfoNotice] = usePersistedState( `logs_pane.dismissed_info_notice`, false ) const surveyLink = variantWithFirstErrorPopup ? 'https://forms.gle/AUbDDRvroQ7KFwHR9' : 'https://forms.gle/bRxevtGzBHRk8BKw8' function handleDismissButtonClick() { setDismissedInfoNotice(true) } return dismissedInfoNotice ? null : ( <div className="log-entry"> <div className="log-entry-header log-entry-header-raw"> <div className="log-entry-header-icon-container"> <span className="info-badge" /> </div> <h3 className="log-entry-header-title"> {t('logs_pane_info_message')} </h3> <a href={surveyLink} 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> ) } LogsPaneInfoNotice.propTypes = { variantWithFirstErrorPopup: PropTypes.bool, } 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, variantWithFirstErrorPopup: PropTypes.bool, onLogEntryLocationClick: PropTypes.func.isRequired, onClearCache: PropTypes.func.isRequired, validationIssues: PropTypes.object, errors: PropTypes.object, } export default PreviewLogsPane