preview-logs-pane.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. import { useTranslation } from 'react-i18next'
  4. import { Dropdown } from 'react-bootstrap'
  5. import PreviewLogsPaneEntry from './preview-logs-pane-entry'
  6. import PreviewValidationIssue from './preview-validation-issue'
  7. import PreviewDownloadFileList from './preview-download-file-list'
  8. import PreviewError from './preview-error'
  9. import Icon from '../../../shared/components/icon'
  10. import usePersistedState from '../../../infrastructure/persisted-state-hook'
  11. function PreviewLogsPane({
  12. logEntries = { all: [], errors: [], warnings: [], typesetting: [] },
  13. rawLog = '',
  14. validationIssues = {},
  15. errors = {},
  16. outputFiles = [],
  17. isClearingCache,
  18. isCompiling = false,
  19. autoCompileHasLintingError = false,
  20. onLogEntryLocationClick,
  21. onClearCache
  22. }) {
  23. const { t } = useTranslation()
  24. const {
  25. all: allCompilerIssues = [],
  26. errors: compilerErrors = [],
  27. warnings: compilerWarnings = [],
  28. typesetting: compilerTypesettingIssues = []
  29. } = logEntries
  30. const errorsUI = Object.keys(errors).map((name, index) => (
  31. <PreviewError key={index} name={name} />
  32. ))
  33. const validationIssuesUI = Object.keys(
  34. validationIssues
  35. ).map((name, index) => (
  36. <PreviewValidationIssue
  37. key={index}
  38. name={name}
  39. details={validationIssues[name]}
  40. />
  41. ))
  42. const logEntriesUI = [
  43. ...compilerErrors,
  44. ...compilerWarnings,
  45. ...compilerTypesettingIssues
  46. ].map((logEntry, idx) => (
  47. <PreviewLogsPaneEntry
  48. key={idx}
  49. headerTitle={logEntry.message}
  50. rawContent={logEntry.content}
  51. logType={logEntry.type}
  52. formattedContent={logEntry.humanReadableHintComponent}
  53. extraInfoURL={logEntry.extraInfoURL}
  54. level={logEntry.level}
  55. entryAriaLabel={t('log_entry_description', {
  56. level: logEntry.level
  57. })}
  58. sourceLocation={{
  59. file: logEntry.file,
  60. line: logEntry.line,
  61. column: logEntry.column
  62. }}
  63. onSourceLocationClick={onLogEntryLocationClick}
  64. />
  65. ))
  66. const actionsUI = (
  67. <div className="logs-pane-actions">
  68. <button
  69. className="btn btn-sm btn-danger logs-pane-actions-clear-cache"
  70. onClick={onClearCache}
  71. disabled={isClearingCache || isCompiling}
  72. >
  73. {isClearingCache ? (
  74. <Icon type="refresh" spin />
  75. ) : (
  76. <Icon type="trash-o" />
  77. )}
  78. &nbsp;
  79. <span>{t('clear_cached_files')}</span>
  80. </button>
  81. <Dropdown
  82. id="dropdown-files-logs-pane"
  83. dropup
  84. pullRight
  85. disabled={isCompiling}
  86. >
  87. <Dropdown.Toggle
  88. className="btn btn-sm btn-info dropdown-toggle"
  89. title={t('other_logs_and_files')}
  90. bsStyle="info"
  91. />
  92. <Dropdown.Menu id="dropdown-files-logs-pane-list">
  93. <PreviewDownloadFileList fileList={outputFiles} />
  94. </Dropdown.Menu>
  95. </Dropdown>
  96. </div>
  97. )
  98. const rawLogUI = (
  99. <PreviewLogsPaneEntry
  100. headerTitle={t('raw_logs')}
  101. rawContent={rawLog}
  102. entryAriaLabel={t('raw_logs_description')}
  103. level="raw"
  104. />
  105. )
  106. return (
  107. <div className="logs-pane">
  108. <div className="logs-pane-content">
  109. <LogsPaneBetaNotice />
  110. {autoCompileHasLintingError ? <AutoCompileLintingErrorEntry /> : null}
  111. {errors ? errorsUI : null}
  112. {validationIssues ? validationIssuesUI : null}
  113. {allCompilerIssues.length > 0 ? logEntriesUI : null}
  114. {rawLog && rawLog !== '' ? rawLogUI : null}
  115. {actionsUI}
  116. </div>
  117. </div>
  118. )
  119. }
  120. function AutoCompileLintingErrorEntry() {
  121. const { t } = useTranslation()
  122. return (
  123. <div className="log-entry">
  124. <div className="log-entry-header log-entry-header-error">
  125. <div className="log-entry-header-icon-container">
  126. <Icon type="exclamation-triangle" modifier="fw" />
  127. </div>
  128. <h3 className="log-entry-header-title">
  129. {t('code_check_failed_explanation')}
  130. </h3>
  131. </div>
  132. </div>
  133. )
  134. }
  135. function LogsPaneBetaNotice() {
  136. const { t } = useTranslation()
  137. const [dismissedBetaNotice, setDismissedBetaNotice] = usePersistedState(
  138. `logs_pane.dismissed_beta_notice`,
  139. false
  140. )
  141. function handleDismissButtonClick() {
  142. setDismissedBetaNotice(true)
  143. }
  144. return dismissedBetaNotice ? null : (
  145. <div className="log-entry">
  146. <div className="log-entry-header log-entry-header-raw">
  147. <div className="log-entry-header-icon-container">
  148. <span className="beta-badge" />
  149. </div>
  150. <h3 className="log-entry-header-title">
  151. {t('logs_pane_beta_message')}
  152. </h3>
  153. <a
  154. href="/beta/participate"
  155. target="_blank"
  156. rel="noopener noreferrer"
  157. className="log-entry-header-link log-entry-header-link-raw"
  158. >
  159. <span className="log-entry-header-link-location">
  160. {t('give_feedback')}
  161. </span>
  162. </a>
  163. <button
  164. className="btn-inline-link log-entry-header-link"
  165. type="button"
  166. aria-label={t('dismiss')}
  167. onClick={handleDismissButtonClick}
  168. >
  169. <span aria-hidden="true">&times;</span>
  170. </button>
  171. </div>
  172. </div>
  173. )
  174. }
  175. PreviewLogsPane.propTypes = {
  176. logEntries: PropTypes.shape({
  177. all: PropTypes.array,
  178. errors: PropTypes.array,
  179. warning: PropTypes.array,
  180. typesetting: PropTypes.array
  181. }),
  182. autoCompileHasLintingError: PropTypes.bool,
  183. rawLog: PropTypes.string,
  184. outputFiles: PropTypes.array,
  185. isClearingCache: PropTypes.bool,
  186. isCompiling: PropTypes.bool,
  187. onLogEntryLocationClick: PropTypes.func.isRequired,
  188. onClearCache: PropTypes.func.isRequired,
  189. validationIssues: PropTypes.object,
  190. errors: PropTypes.object
  191. }
  192. export default PreviewLogsPane