preview-toolbar.js 7.8 KB

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