preview-toolbar.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import React, { useRef, useState } from 'react'
  2. import PropTypes from 'prop-types'
  3. import { OverlayTrigger, Tooltip } from 'react-bootstrap'
  4. import { useTranslation } from 'react-i18next'
  5. import PreviewDownloadButton from './preview-download-button'
  6. import PreviewRecompileButton from './preview-recompile-button'
  7. import PreviewLogsToggleButton from './preview-logs-toggle-button'
  8. import useResizeObserver from '../../../shared/hooks/use-resize-observer'
  9. import Icon from '../../../shared/components/icon'
  10. function _getElementWidth(element) {
  11. if (!element) return 0
  12. return Math.ceil(element.getBoundingClientRect().width)
  13. }
  14. function PreviewToolbar({
  15. compilerState,
  16. logsState,
  17. onRecompileFromScratch,
  18. onRecompile,
  19. onRunSyntaxCheckNow,
  20. onSetAutoCompile,
  21. onSetDraftMode,
  22. onSetSyntaxCheck,
  23. onToggleLogs,
  24. onSetSplitLayout,
  25. onSetFullLayout,
  26. onStopCompilation,
  27. outputFiles,
  28. pdfDownloadUrl,
  29. showLogs,
  30. splitLayout
  31. }) {
  32. const showTextRef = useRef(true)
  33. const showToggleTextRef = useRef(true)
  34. const toolbarRef = useRef()
  35. const recompileWidthDifferenceRef = useRef()
  36. const recompileLongerTextRef = useRef()
  37. const [showText, setShowText] = useState(showTextRef.current)
  38. const [showToggleText, setShowToggleText] = useState(
  39. showToggleTextRef.current
  40. )
  41. const { t } = useTranslation()
  42. function checkCanShowText(observedElement) {
  43. // toolbar items can be in 3 states:
  44. // all text, only toggle logs w/text and icons on others, all icons
  45. // states depend on available space in the toolbar
  46. const toolbarWidth =
  47. observedElement &&
  48. observedElement.contentRect &&
  49. observedElement.contentRect.width
  50. if (!toolbarWidth) return
  51. _checkRecompileStateWidths()
  52. let textWidths = 0
  53. let itemsWidth = _getItemsWidth() // could be with or without text
  54. // get widths of text only
  55. // required for _checkToggleText, even if currently showing text
  56. const textElements = toolbarRef.current.querySelectorAll('.toolbar-text')
  57. textElements.forEach(item => {
  58. if (item.getAttribute('aria-hidden') !== 'true') {
  59. textWidths += _getElementWidth(item)
  60. }
  61. })
  62. const logsToggleText = toolbarRef.current.querySelector(
  63. '#logs-toggle .toolbar-text'
  64. )
  65. const logsToggleTextWidth = _getElementWidth(logsToggleText)
  66. if (!showTextRef.current && !showToggleTextRef.current) {
  67. // itemsWidth was calculated without any text shown
  68. itemsWidth += textWidths
  69. } else if (!showTextRef.current && showToggleTextRef.current) {
  70. // itemsWidth was calculated with toggle text but no other text
  71. // only add text width for other items and then
  72. // subtract toggle text width, since it is already in itemsWidth
  73. itemsWidth += parseInt(textWidths - logsToggleTextWidth, 10)
  74. }
  75. // only add extra if recompile button is in state with smaller length
  76. if (
  77. recompileWidthDifferenceRef.current &&
  78. recompileLongerTextRef.current &&
  79. ((!compilerState.isCompiling &&
  80. recompileLongerTextRef.current === 'compiling') ||
  81. (compilerState.isCompiling &&
  82. recompileLongerTextRef.current === 'recompile'))
  83. ) {
  84. itemsWidth += recompileWidthDifferenceRef.current
  85. }
  86. itemsWidth += 10 // add extra for some spacing between items
  87. let canShowText = itemsWidth < toolbarWidth
  88. if (!canShowText) {
  89. _checkToggleText(
  90. toolbarWidth,
  91. logsToggleTextWidth,
  92. itemsWidth,
  93. textWidths
  94. )
  95. } else if (showToggleTextRef.current !== true) {
  96. setShowToggleText(true)
  97. showToggleTextRef.current = true
  98. }
  99. setShowText(canShowText)
  100. showTextRef.current = canShowText
  101. }
  102. function _checkRecompileStateWidths() {
  103. // check recompile/compiling button text.
  104. // Do not want to hide and then show text when recompiling
  105. if (
  106. recompileWidthDifferenceRef.current ||
  107. recompileWidthDifferenceRef.current === 0
  108. )
  109. return
  110. const textCompiling = toolbarRef.current.querySelector('#text-compiling')
  111. const textRecompile = toolbarRef.current.querySelector('#text-recompile')
  112. const textCompilingWidth = _getElementWidth(textCompiling)
  113. const textRecompileWidth = _getElementWidth(textRecompile)
  114. const textLengthDifference = Math.abs(
  115. parseInt(textCompilingWidth - textRecompileWidth, 10)
  116. )
  117. recompileWidthDifferenceRef.current = textLengthDifference
  118. // ignore if equal
  119. if (textRecompileWidth > textCompilingWidth) {
  120. recompileLongerTextRef.current = 'recompile'
  121. } else if (textRecompileWidth < textCompilingWidth) {
  122. recompileLongerTextRef.current = 'compiling'
  123. }
  124. }
  125. function _checkToggleText(
  126. toolbarWidth,
  127. logsToggleTextWidth,
  128. itemsWithTextWidth,
  129. textWidths
  130. ) {
  131. // check to see if we can still show the toggle button text
  132. let toggleWithTextWidth = 0
  133. let toggleWithoutTextWidth = 0
  134. const itemsWithoutTextWidth = parseInt(itemsWithTextWidth - textWidths, 10)
  135. const logsToggle = toolbarRef.current.querySelector('#logs-toggle')
  136. const logsToggleWidth = _getElementWidth(logsToggle)
  137. // logsToggleWidth could be with or without text
  138. if (showToggleTextRef.current) {
  139. toggleWithTextWidth = logsToggleWidth
  140. toggleWithoutTextWidth = parseInt(
  141. logsToggleWidth - logsToggleTextWidth,
  142. 10
  143. )
  144. } else {
  145. toggleWithTextWidth = parseInt(logsToggleWidth + logsToggleTextWidth, 10)
  146. toggleWithoutTextWidth = logsToggleWidth
  147. }
  148. const itemsWithoutTextAndToggleWidth = parseInt(
  149. itemsWithoutTextWidth - toggleWithoutTextWidth,
  150. 10
  151. )
  152. const itemsWithIconsExceptToggleWidth = parseInt(
  153. itemsWithoutTextAndToggleWidth + toggleWithTextWidth,
  154. 10
  155. )
  156. const canShowToggleText = itemsWithIconsExceptToggleWidth < toolbarWidth
  157. if (canShowToggleText !== showToggleTextRef.current) {
  158. setShowToggleText(canShowToggleText)
  159. showToggleTextRef.current = canShowToggleText
  160. }
  161. }
  162. function _getItemsWidth() {
  163. const toolbarItems = toolbarRef.current.querySelectorAll('.toolbar-item')
  164. let itemWidth = 0
  165. toolbarItems.forEach(item => {
  166. itemWidth += _getElementWidth(item)
  167. })
  168. return itemWidth
  169. }
  170. const pdfExpandLabel = splitLayout ? t('full_screen') : t('split_screen')
  171. const pdfExpandIconType = splitLayout ? 'expand' : 'compress'
  172. const pdfExpandTooltip = (
  173. <Tooltip id="expand-pdf-btn">{pdfExpandLabel}</Tooltip>
  174. )
  175. function handlePdfExpandBtnClick() {
  176. if (splitLayout) {
  177. onSetFullLayout()
  178. } else {
  179. onSetSplitLayout()
  180. }
  181. }
  182. useResizeObserver(toolbarRef, logsState, checkCanShowText)
  183. return (
  184. <div
  185. className="toolbar toolbar-pdf"
  186. id="toolbar-preview"
  187. data-testid="toolbar-preview"
  188. ref={toolbarRef}
  189. >
  190. <div className="toolbar-pdf-left">
  191. <PreviewRecompileButton
  192. compilerState={compilerState}
  193. onRecompile={onRecompile}
  194. onRecompileFromScratch={onRecompileFromScratch}
  195. onRunSyntaxCheckNow={onRunSyntaxCheckNow}
  196. onSetAutoCompile={onSetAutoCompile}
  197. onSetDraftMode={onSetDraftMode}
  198. onSetSyntaxCheck={onSetSyntaxCheck}
  199. onStopCompilation={onStopCompilation}
  200. showText={showText}
  201. />
  202. <PreviewDownloadButton
  203. isCompiling={compilerState.isCompiling}
  204. outputFiles={outputFiles}
  205. pdfDownloadUrl={pdfDownloadUrl}
  206. showText={showText}
  207. />
  208. </div>
  209. <div className="toolbar-pdf-right">
  210. <PreviewLogsToggleButton
  211. logsState={logsState}
  212. showLogs={showLogs}
  213. compileFailed={compilerState.compileFailed}
  214. onToggle={onToggleLogs}
  215. showText={showToggleText}
  216. />
  217. <OverlayTrigger placement="left" overlay={pdfExpandTooltip}>
  218. <button
  219. onClick={handlePdfExpandBtnClick}
  220. className="toolbar-pdf-expand-btn toolbar-item"
  221. aria-label={pdfExpandLabel}
  222. >
  223. <Icon type={pdfExpandIconType} />
  224. </button>
  225. </OverlayTrigger>
  226. </div>
  227. </div>
  228. )
  229. }
  230. PreviewToolbar.propTypes = {
  231. compilerState: PropTypes.shape({
  232. isAutoCompileOn: PropTypes.bool.isRequired,
  233. isCompiling: PropTypes.bool.isRequired,
  234. isDraftModeOn: PropTypes.bool.isRequired,
  235. isSyntaxCheckOn: PropTypes.bool.isRequired,
  236. compileFailed: PropTypes.bool,
  237. logEntries: PropTypes.object.isRequired
  238. }),
  239. logsState: PropTypes.shape({
  240. nErrors: PropTypes.number.isRequired,
  241. nWarnings: PropTypes.number.isRequired,
  242. nLogEntries: PropTypes.number.isRequired
  243. }),
  244. showLogs: PropTypes.bool.isRequired,
  245. splitLayout: PropTypes.bool.isRequired,
  246. onRecompile: PropTypes.func.isRequired,
  247. onRecompileFromScratch: PropTypes.func.isRequired,
  248. onRunSyntaxCheckNow: PropTypes.func.isRequired,
  249. onSetAutoCompile: PropTypes.func.isRequired,
  250. onSetDraftMode: PropTypes.func.isRequired,
  251. onSetSyntaxCheck: PropTypes.func.isRequired,
  252. onToggleLogs: PropTypes.func.isRequired,
  253. onSetSplitLayout: PropTypes.func.isRequired,
  254. onSetFullLayout: PropTypes.func.isRequired,
  255. onStopCompilation: PropTypes.func.isRequired,
  256. pdfDownloadUrl: PropTypes.string,
  257. outputFiles: PropTypes.array
  258. }
  259. export default PreviewToolbar