pdf-compile-button.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import { useTranslation } from 'react-i18next'
  2. import { memo } from 'react'
  3. import classNames from 'classnames'
  4. import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
  5. import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
  6. import SplitMenu from '../../../shared/components/split-menu'
  7. import Icon from '../../../shared/components/icon'
  8. import * as eventTracking from '../../../infrastructure/event-tracking'
  9. import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
  10. import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
  11. import { Spinner } from 'react-bootstrap-5'
  12. import {
  13. Dropdown,
  14. DropdownDivider,
  15. DropdownHeader,
  16. DropdownItem,
  17. DropdownMenu,
  18. DropdownToggle,
  19. } from '@/features/ui/components/bootstrap-5/dropdown-menu'
  20. import OLButton from '@/features/ui/components/ol/ol-button'
  21. import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
  22. import { bsVersion } from '@/features/utils/bootstrap-5'
  23. const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
  24. function sendEventAndSet<T extends boolean>(
  25. value: T,
  26. setter: (value: T) => void,
  27. settingName: string
  28. ) {
  29. eventTracking.sendMB('recompile-setting-changed', {
  30. setting: settingName,
  31. settingVal: value,
  32. })
  33. setter(value)
  34. }
  35. function PdfCompileButton() {
  36. const {
  37. animateCompileDropdownArrow,
  38. autoCompile,
  39. compiling,
  40. draft,
  41. hasChanges,
  42. setAnimateCompileDropdownArrow,
  43. setAutoCompile,
  44. setDraft,
  45. setStopOnValidationError,
  46. stopOnFirstError,
  47. stopOnValidationError,
  48. startCompile,
  49. stopCompile,
  50. recompileFromScratch,
  51. } = useCompileContext()
  52. const { enableStopOnFirstError, disableStopOnFirstError } =
  53. useStopOnFirstError({ eventSource: 'dropdown' })
  54. const { t } = useTranslation()
  55. const fromScratchWithEvent = () => {
  56. eventTracking.sendMB('recompile-setting-changed', {
  57. setting: 'from-scratch',
  58. })
  59. recompileFromScratch()
  60. }
  61. const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
  62. const tooltipElement = (
  63. <>
  64. {t('recompile_pdf')}{' '}
  65. <span className="keyboard-shortcut">({modifierKey} + Enter)</span>
  66. </>
  67. )
  68. const dropdownToggleClassName = classNames(
  69. {
  70. 'detach-compile-button-animate': animateCompileDropdownArrow,
  71. 'btn-striped-animated': hasChanges,
  72. 'no-left-border': true,
  73. },
  74. bsVersion({ bs5: 'dropdown-button-toggle' })
  75. )
  76. const buttonClassName = classNames({
  77. 'btn-striped-animated': hasChanges,
  78. 'no-left-radius': true,
  79. })
  80. return (
  81. <BootstrapVersionSwitcher
  82. bs3={
  83. <SplitMenu
  84. bsStyle="primary"
  85. bsSize="xs"
  86. disabled={compiling}
  87. button={{
  88. tooltip: {
  89. description: tooltipElement,
  90. id: 'compile',
  91. tooltipProps: { className: 'keyboard-tooltip' },
  92. overlayProps: { delayShow: 500 },
  93. },
  94. icon: { type: 'refresh', spin: compiling },
  95. onClick: () => startCompile(),
  96. text: compileButtonLabel,
  97. className: buttonClassName,
  98. }}
  99. dropdownToggle={{
  100. 'aria-label': t('toggle_compile_options_menu'),
  101. handleAnimationEnd: () => setAnimateCompileDropdownArrow(false),
  102. className: dropdownToggleClassName,
  103. }}
  104. dropdown={{
  105. id: 'pdf-recompile-dropdown',
  106. }}
  107. >
  108. <SplitMenu.Item header>{t('auto_compile')}</SplitMenu.Item>
  109. <SplitMenu.Item
  110. onSelect={() =>
  111. sendEventAndSet(true, setAutoCompile, 'auto-compile')
  112. }
  113. >
  114. <Icon type={autoCompile ? 'check' : ''} fw />
  115. {t('on')}
  116. </SplitMenu.Item>
  117. <SplitMenu.Item
  118. onSelect={() =>
  119. sendEventAndSet(false, setAutoCompile, 'auto-compile')
  120. }
  121. >
  122. <Icon type={!autoCompile ? 'check' : ''} fw />
  123. {t('off')}
  124. </SplitMenu.Item>
  125. <SplitMenu.Item header>{t('compile_mode')}</SplitMenu.Item>
  126. <SplitMenu.Item
  127. onSelect={() => sendEventAndSet(false, setDraft, 'compile-mode')}
  128. >
  129. <Icon type={!draft ? 'check' : ''} fw />
  130. {t('normal')}
  131. </SplitMenu.Item>
  132. <SplitMenu.Item
  133. onSelect={() => sendEventAndSet(true, setDraft, 'compile-mode')}
  134. >
  135. <Icon type={draft ? 'check' : ''} fw />
  136. {t('fast')} <span className="subdued">[draft]</span>
  137. </SplitMenu.Item>
  138. <SplitMenu.Item header>Syntax Checks</SplitMenu.Item>
  139. <SplitMenu.Item
  140. onSelect={() =>
  141. sendEventAndSet(true, setStopOnValidationError, 'syntax-check')
  142. }
  143. >
  144. <Icon type={stopOnValidationError ? 'check' : ''} fw />
  145. {t('stop_on_validation_error')}
  146. </SplitMenu.Item>
  147. <SplitMenu.Item
  148. onSelect={() =>
  149. sendEventAndSet(false, setStopOnValidationError, 'syntax-check')
  150. }
  151. >
  152. <Icon type={!stopOnValidationError ? 'check' : ''} fw />
  153. {t('ignore_validation_errors')}
  154. </SplitMenu.Item>
  155. <SplitMenu.Item header>{t('compile_error_handling')}</SplitMenu.Item>
  156. <SplitMenu.Item onSelect={enableStopOnFirstError}>
  157. <Icon type={stopOnFirstError ? 'check' : ''} fw />
  158. {t('stop_on_first_error')}
  159. </SplitMenu.Item>
  160. <SplitMenu.Item onSelect={disableStopOnFirstError}>
  161. <Icon type={!stopOnFirstError ? 'check' : ''} fw />
  162. {t('try_to_compile_despite_errors')}
  163. </SplitMenu.Item>
  164. <SplitMenu.Item divider />
  165. <SplitMenu.Item
  166. onSelect={() => stopCompile()}
  167. disabled={!compiling}
  168. aria-disabled={!compiling}
  169. >
  170. {t('stop_compile')}
  171. </SplitMenu.Item>
  172. <SplitMenu.Item
  173. onSelect={fromScratchWithEvent}
  174. disabled={compiling}
  175. aria-disabled={compiling}
  176. >
  177. {t('recompile_from_scratch')}
  178. </SplitMenu.Item>
  179. </SplitMenu>
  180. }
  181. bs5={
  182. <Dropdown as={OLButtonGroup} autoClose="outside">
  183. <OLTooltip
  184. description={tooltipElement}
  185. id="compile"
  186. tooltipProps={{ className: 'keyboard-tooltip' }}
  187. overlayProps={{ delay: { show: 500, hide: 0 } }}
  188. >
  189. <OLButton
  190. variant="primary"
  191. size="sm"
  192. disabled={compiling}
  193. onClick={() => startCompile()}
  194. leadingIcon={
  195. compiling ? (
  196. <Spinner
  197. animation="border"
  198. aria-hidden="true"
  199. size="sm"
  200. role="status"
  201. />
  202. ) : null
  203. }
  204. className={buttonClassName}
  205. >
  206. {compileButtonLabel}
  207. </OLButton>
  208. </OLTooltip>
  209. <DropdownToggle
  210. split
  211. variant="primary"
  212. id="pdf-recompile-dropdown"
  213. size="sm"
  214. aria-label={t('toggle_compile_options_menu')}
  215. className={dropdownToggleClassName}
  216. />
  217. <DropdownMenu>
  218. <DropdownHeader>{t('auto_compile')}</DropdownHeader>
  219. <li role="none">
  220. <DropdownItem
  221. as="button"
  222. onClick={() =>
  223. sendEventAndSet(true, setAutoCompile, 'auto-compile')
  224. }
  225. trailingIcon={autoCompile ? 'check' : null}
  226. >
  227. {t('on')}
  228. </DropdownItem>
  229. </li>
  230. <li role="none">
  231. <DropdownItem
  232. as="button"
  233. onClick={() =>
  234. sendEventAndSet(false, setAutoCompile, 'auto-compile')
  235. }
  236. trailingIcon={!autoCompile ? 'check' : null}
  237. >
  238. {t('off')}
  239. </DropdownItem>
  240. </li>
  241. <DropdownDivider />
  242. <DropdownHeader>{t('compile_mode')}</DropdownHeader>
  243. <li role="none">
  244. <DropdownItem
  245. as="button"
  246. onClick={() => sendEventAndSet(false, setDraft, 'compile-mode')}
  247. trailingIcon={!draft ? 'check' : null}
  248. >
  249. {t('normal')}
  250. </DropdownItem>
  251. </li>
  252. <li role="none">
  253. <DropdownItem
  254. as="button"
  255. onClick={() => sendEventAndSet(true, setDraft, 'compile-mode')}
  256. trailingIcon={draft ? 'check' : null}
  257. >
  258. {t('fast')}&nbsp;<span className="subdued">[draft]</span>
  259. </DropdownItem>
  260. </li>
  261. <DropdownDivider />
  262. <DropdownHeader>Syntax Checks</DropdownHeader>
  263. <li role="none">
  264. <DropdownItem
  265. as="button"
  266. onClick={() =>
  267. sendEventAndSet(
  268. true,
  269. setStopOnValidationError,
  270. 'syntax-check'
  271. )
  272. }
  273. trailingIcon={stopOnValidationError ? 'check' : null}
  274. >
  275. {t('stop_on_validation_error')}
  276. </DropdownItem>
  277. </li>
  278. <li role="none">
  279. <DropdownItem
  280. as="button"
  281. onClick={() =>
  282. sendEventAndSet(
  283. false,
  284. setStopOnValidationError,
  285. 'syntax-check'
  286. )
  287. }
  288. trailingIcon={!stopOnValidationError ? 'check' : null}
  289. >
  290. {t('ignore_validation_errors')}
  291. </DropdownItem>
  292. </li>
  293. <DropdownDivider />
  294. <DropdownHeader>{t('compile_error_handling')}</DropdownHeader>
  295. <li role="none">
  296. <DropdownItem
  297. as="button"
  298. onClick={enableStopOnFirstError}
  299. trailingIcon={stopOnFirstError ? 'check' : null}
  300. >
  301. {t('stop_on_first_error')}
  302. </DropdownItem>
  303. </li>
  304. <li role="none">
  305. <DropdownItem
  306. as="button"
  307. onClick={disableStopOnFirstError}
  308. trailingIcon={!stopOnFirstError ? 'check' : null}
  309. >
  310. {t('try_to_compile_despite_errors')}
  311. </DropdownItem>
  312. </li>
  313. <DropdownDivider />
  314. <li role="none">
  315. <DropdownItem
  316. as="button"
  317. onClick={() => stopCompile()}
  318. disabled={!compiling}
  319. aria-disabled={!compiling}
  320. >
  321. {t('stop_compile')}
  322. </DropdownItem>
  323. </li>
  324. <li role="none">
  325. <DropdownItem
  326. as="button"
  327. onClick={fromScratchWithEvent}
  328. disabled={compiling}
  329. aria-disabled={compiling}
  330. >
  331. {t('recompile_from_scratch')}
  332. </DropdownItem>
  333. </li>
  334. </DropdownMenu>
  335. </Dropdown>
  336. }
  337. />
  338. )
  339. }
  340. export default memo(PdfCompileButton)