|
|
@@ -0,0 +1,366 @@
|
|
|
+import { useTranslation } from 'react-i18next'
|
|
|
+import { memo } from 'react'
|
|
|
+import classNames from 'classnames'
|
|
|
+import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
|
|
+import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
|
|
|
+import SplitMenu from '../../../shared/components/split-menu'
|
|
|
+import Icon from '../../../shared/components/icon'
|
|
|
+import * as eventTracking from '../../../infrastructure/event-tracking'
|
|
|
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
|
|
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
|
|
+import { Spinner } from 'react-bootstrap-5'
|
|
|
+import {
|
|
|
+ Dropdown,
|
|
|
+ DropdownDivider,
|
|
|
+ DropdownHeader,
|
|
|
+ DropdownItem,
|
|
|
+ DropdownMenu,
|
|
|
+ DropdownToggle,
|
|
|
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
|
|
+import OLButton from '@/features/ui/components/ol/ol-button'
|
|
|
+import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
|
|
|
+import { bsVersion } from '@/features/utils/bootstrap-5'
|
|
|
+
|
|
|
+const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
|
|
+
|
|
|
+function sendEventAndSet<T extends boolean>(
|
|
|
+ value: T,
|
|
|
+ setter: (value: T) => void,
|
|
|
+ settingName: string
|
|
|
+) {
|
|
|
+ eventTracking.sendMB('recompile-setting-changed', {
|
|
|
+ setting: settingName,
|
|
|
+ settingVal: value,
|
|
|
+ })
|
|
|
+ setter(value)
|
|
|
+}
|
|
|
+
|
|
|
+function PdfCompileButton() {
|
|
|
+ const {
|
|
|
+ animateCompileDropdownArrow,
|
|
|
+ autoCompile,
|
|
|
+ compiling,
|
|
|
+ draft,
|
|
|
+ hasChanges,
|
|
|
+ setAnimateCompileDropdownArrow,
|
|
|
+ setAutoCompile,
|
|
|
+ setDraft,
|
|
|
+ setStopOnValidationError,
|
|
|
+ stopOnFirstError,
|
|
|
+ stopOnValidationError,
|
|
|
+ startCompile,
|
|
|
+ stopCompile,
|
|
|
+ recompileFromScratch,
|
|
|
+ } = useCompileContext()
|
|
|
+ const { enableStopOnFirstError, disableStopOnFirstError } =
|
|
|
+ useStopOnFirstError({ eventSource: 'dropdown' })
|
|
|
+
|
|
|
+ const { t } = useTranslation()
|
|
|
+
|
|
|
+ const fromScratchWithEvent = () => {
|
|
|
+ eventTracking.sendMB('recompile-setting-changed', {
|
|
|
+ setting: 'from-scratch',
|
|
|
+ })
|
|
|
+ recompileFromScratch()
|
|
|
+ }
|
|
|
+
|
|
|
+ const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
|
|
|
+ const tooltipElement = (
|
|
|
+ <>
|
|
|
+ {t('recompile_pdf')}{' '}
|
|
|
+ <span className="keyboard-shortcut">({modifierKey} + Enter)</span>
|
|
|
+ </>
|
|
|
+ )
|
|
|
+
|
|
|
+ const dropdownToggleClassName = classNames(
|
|
|
+ {
|
|
|
+ 'detach-compile-button-animate': animateCompileDropdownArrow,
|
|
|
+ 'btn-striped-animated': hasChanges,
|
|
|
+ 'no-left-border': true,
|
|
|
+ },
|
|
|
+ bsVersion({ bs5: 'dropdown-button-toggle' })
|
|
|
+ )
|
|
|
+
|
|
|
+ const buttonClassName = classNames({
|
|
|
+ 'btn-striped-animated': hasChanges,
|
|
|
+ 'no-left-radius': true,
|
|
|
+ })
|
|
|
+
|
|
|
+ return (
|
|
|
+ <BootstrapVersionSwitcher
|
|
|
+ bs3={
|
|
|
+ <SplitMenu
|
|
|
+ bsStyle="primary"
|
|
|
+ bsSize="xs"
|
|
|
+ disabled={compiling}
|
|
|
+ button={{
|
|
|
+ tooltip: {
|
|
|
+ description: tooltipElement,
|
|
|
+ id: 'compile',
|
|
|
+ tooltipProps: { className: 'keyboard-tooltip' },
|
|
|
+ overlayProps: { delayShow: 500 },
|
|
|
+ },
|
|
|
+ icon: { type: 'refresh', spin: compiling },
|
|
|
+ onClick: () => startCompile(),
|
|
|
+ text: compileButtonLabel,
|
|
|
+ className: buttonClassName,
|
|
|
+ }}
|
|
|
+ dropdownToggle={{
|
|
|
+ 'aria-label': t('toggle_compile_options_menu'),
|
|
|
+ handleAnimationEnd: () => setAnimateCompileDropdownArrow(false),
|
|
|
+ className: dropdownToggleClassName,
|
|
|
+ }}
|
|
|
+ dropdown={{
|
|
|
+ id: 'pdf-recompile-dropdown',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <SplitMenu.Item header>{t('auto_compile')}</SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() =>
|
|
|
+ sendEventAndSet(true, setAutoCompile, 'auto-compile')
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Icon type={autoCompile ? 'check' : ''} fw />
|
|
|
+ {t('on')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() =>
|
|
|
+ sendEventAndSet(false, setAutoCompile, 'auto-compile')
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Icon type={!autoCompile ? 'check' : ''} fw />
|
|
|
+ {t('off')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item header>{t('compile_mode')}</SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() => sendEventAndSet(false, setDraft, 'compile-mode')}
|
|
|
+ >
|
|
|
+ <Icon type={!draft ? 'check' : ''} fw />
|
|
|
+ {t('normal')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() => sendEventAndSet(true, setDraft, 'compile-mode')}
|
|
|
+ >
|
|
|
+ <Icon type={draft ? 'check' : ''} fw />
|
|
|
+ {t('fast')} <span className="subdued">[draft]</span>
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item header>Syntax Checks</SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() =>
|
|
|
+ sendEventAndSet(true, setStopOnValidationError, 'syntax-check')
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Icon type={stopOnValidationError ? 'check' : ''} fw />
|
|
|
+ {t('stop_on_validation_error')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() =>
|
|
|
+ sendEventAndSet(false, setStopOnValidationError, 'syntax-check')
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Icon type={!stopOnValidationError ? 'check' : ''} fw />
|
|
|
+ {t('ignore_validation_errors')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item header>{t('compile_error_handling')}</SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item onSelect={enableStopOnFirstError}>
|
|
|
+ <Icon type={stopOnFirstError ? 'check' : ''} fw />
|
|
|
+ {t('stop_on_first_error')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item onSelect={disableStopOnFirstError}>
|
|
|
+ <Icon type={!stopOnFirstError ? 'check' : ''} fw />
|
|
|
+ {t('try_to_compile_despite_errors')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item divider />
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={() => stopCompile()}
|
|
|
+ disabled={!compiling}
|
|
|
+ aria-disabled={!compiling}
|
|
|
+ >
|
|
|
+ {t('stop_compile')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+
|
|
|
+ <SplitMenu.Item
|
|
|
+ onSelect={fromScratchWithEvent}
|
|
|
+ disabled={compiling}
|
|
|
+ aria-disabled={compiling}
|
|
|
+ >
|
|
|
+ {t('recompile_from_scratch')}
|
|
|
+ </SplitMenu.Item>
|
|
|
+ </SplitMenu>
|
|
|
+ }
|
|
|
+ bs5={
|
|
|
+ <Dropdown as={OLButtonGroup} autoClose="outside">
|
|
|
+ <OLTooltip
|
|
|
+ description={tooltipElement}
|
|
|
+ id="compile"
|
|
|
+ tooltipProps={{ className: 'keyboard-tooltip' }}
|
|
|
+ overlayProps={{ delay: { show: 500, hide: 0 } }}
|
|
|
+ >
|
|
|
+ <OLButton
|
|
|
+ variant="primary"
|
|
|
+ size="sm"
|
|
|
+ disabled={compiling}
|
|
|
+ onClick={() => startCompile()}
|
|
|
+ leadingIcon={
|
|
|
+ compiling ? (
|
|
|
+ <Spinner
|
|
|
+ animation="border"
|
|
|
+ aria-hidden="true"
|
|
|
+ size="sm"
|
|
|
+ role="status"
|
|
|
+ />
|
|
|
+ ) : null
|
|
|
+ }
|
|
|
+ className={buttonClassName}
|
|
|
+ >
|
|
|
+ {compileButtonLabel}
|
|
|
+ </OLButton>
|
|
|
+ </OLTooltip>
|
|
|
+
|
|
|
+ <DropdownToggle
|
|
|
+ split
|
|
|
+ variant="primary"
|
|
|
+ id="pdf-recompile-dropdown"
|
|
|
+ size="sm"
|
|
|
+ aria-label={t('toggle_compile_options_menu')}
|
|
|
+ className={dropdownToggleClassName}
|
|
|
+ />
|
|
|
+
|
|
|
+ <DropdownMenu>
|
|
|
+ <DropdownHeader>{t('auto_compile')}</DropdownHeader>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() =>
|
|
|
+ sendEventAndSet(true, setAutoCompile, 'auto-compile')
|
|
|
+ }
|
|
|
+ trailingIcon={autoCompile ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('on')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() =>
|
|
|
+ sendEventAndSet(false, setAutoCompile, 'auto-compile')
|
|
|
+ }
|
|
|
+ trailingIcon={!autoCompile ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('off')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <DropdownDivider />
|
|
|
+ <DropdownHeader>{t('compile_mode')}</DropdownHeader>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() => sendEventAndSet(false, setDraft, 'compile-mode')}
|
|
|
+ trailingIcon={!draft ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('normal')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() => sendEventAndSet(true, setDraft, 'compile-mode')}
|
|
|
+ trailingIcon={draft ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('fast')} <span className="subdued">[draft]</span>
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <DropdownDivider />
|
|
|
+ <DropdownHeader>Syntax Checks</DropdownHeader>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() =>
|
|
|
+ sendEventAndSet(
|
|
|
+ true,
|
|
|
+ setStopOnValidationError,
|
|
|
+ 'syntax-check'
|
|
|
+ )
|
|
|
+ }
|
|
|
+ trailingIcon={stopOnValidationError ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('stop_on_validation_error')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() =>
|
|
|
+ sendEventAndSet(
|
|
|
+ false,
|
|
|
+ setStopOnValidationError,
|
|
|
+ 'syntax-check'
|
|
|
+ )
|
|
|
+ }
|
|
|
+ trailingIcon={!stopOnValidationError ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('ignore_validation_errors')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <DropdownDivider />
|
|
|
+ <DropdownHeader>{t('compile_error_handling')}</DropdownHeader>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={enableStopOnFirstError}
|
|
|
+ trailingIcon={stopOnFirstError ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('stop_on_first_error')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={disableStopOnFirstError}
|
|
|
+ trailingIcon={!stopOnFirstError ? 'check' : null}
|
|
|
+ >
|
|
|
+ {t('try_to_compile_despite_errors')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <DropdownDivider />
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={() => stopCompile()}
|
|
|
+ disabled={!compiling}
|
|
|
+ aria-disabled={!compiling}
|
|
|
+ >
|
|
|
+ {t('stop_compile')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ <li role="none">
|
|
|
+ <DropdownItem
|
|
|
+ as="button"
|
|
|
+ onClick={fromScratchWithEvent}
|
|
|
+ disabled={compiling}
|
|
|
+ aria-disabled={compiling}
|
|
|
+ >
|
|
|
+ {t('recompile_from_scratch')}
|
|
|
+ </DropdownItem>
|
|
|
+ </li>
|
|
|
+ </DropdownMenu>
|
|
|
+ </Dropdown>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export default memo(PdfCompileButton)
|