preview-logs-pane.js 5.1 KB

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