preview-logs-pane.js 6.1 KB

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