pdf-compile-button.tsx 11 KB

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