mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 06:03:42 -05:00
Merge pull request #20708 from overleaf/ii-bs5-pdf-toolbar
[web] BS5 pdf toolbar GitOrigin-RevId: a04091c9e936e52f47c3977b3149ffe613d43bb9
This commit is contained in:
parent
9ce7d4ec44
commit
92eade7502
54 changed files with 1384 additions and 595 deletions
|
@ -1,4 +1,4 @@
|
||||||
extends ../layout-marketing
|
extends ../layout-react
|
||||||
|
|
||||||
block entrypointVar
|
block entrypointVar
|
||||||
- entrypoint = 'ide-detached'
|
- entrypoint = 'ide-detached'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
extends ../layout-marketing
|
extends ../layout-react
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- var suppressNavbar = true
|
- var suppressNavbar = true
|
||||||
|
|
|
@ -8,7 +8,7 @@ function BackToEditorButton({ onClick }: { onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<OLButton
|
<OLButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="sm"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="back-to-editor-btn"
|
className="back-to-editor-btn"
|
||||||
>
|
>
|
||||||
|
|
|
@ -15,7 +15,7 @@ function UpgradePrompt() {
|
||||||
return (
|
return (
|
||||||
<OLButton
|
<OLButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="small"
|
size="sm"
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'toolbar-header-upgrade-prompt',
|
'toolbar-header-upgrade-prompt',
|
||||||
bsVersion({ bs3: 'btn-xs' })
|
bsVersion({ bs3: 'btn-xs' })
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import classnames from 'classnames'
|
||||||
import classNames from 'classnames'
|
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
|
import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
||||||
|
|
||||||
|
@ -21,28 +23,49 @@ function DetachCompileButton() {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="detach-compile-button-container">
|
<div
|
||||||
<Tooltip
|
className={classnames(
|
||||||
|
'detach-compile-button-container',
|
||||||
|
bsVersion({ bs5: 'ms-1' })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<OLTooltip
|
||||||
id="detach-compile"
|
id="detach-compile"
|
||||||
description={tooltipElement}
|
description={tooltipElement}
|
||||||
tooltipProps={{ className: 'keyboard-tooltip' }}
|
tooltipProps={{ className: 'keyboard-tooltip' }}
|
||||||
overlayProps={{ delayShow: 500 }}
|
overlayProps={{ delay: { show: 500, hide: 0 } }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle="primary"
|
variant="primary"
|
||||||
onClick={() => startCompile()}
|
onClick={() => startCompile()}
|
||||||
disabled={compiling}
|
disabled={compiling}
|
||||||
className={classNames('detach-compile-button', {
|
className={classnames('detach-compile-button', {
|
||||||
'btn-striped-animated': hasChanges,
|
'btn-striped-animated': hasChanges,
|
||||||
'detach-compile-button-disabled': compiling,
|
'detach-compile-button-disabled': compiling,
|
||||||
})}
|
})}
|
||||||
|
size="sm"
|
||||||
|
isLoading={compiling}
|
||||||
|
bs3Props={{
|
||||||
|
loading: (
|
||||||
|
<>
|
||||||
|
<Icon type="refresh" spin={compiling} />
|
||||||
|
<span className="detach-compile-button-label">
|
||||||
|
{compileButtonLabel}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="refresh" spin={compiling} />
|
<BootstrapVersionSwitcher
|
||||||
<span className="detach-compile-button-label">
|
bs3={
|
||||||
{compileButtonLabel}
|
<span className="detach-compile-button-label">
|
||||||
</span>
|
{compileButtonLabel}
|
||||||
</Button>
|
</span>
|
||||||
</Tooltip>
|
}
|
||||||
|
bs5={compileButtonLabel}
|
||||||
|
/>
|
||||||
|
</OLButton>
|
||||||
|
</OLTooltip>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
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'
|
|
||||||
|
|
||||||
const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
|
||||||
|
|
||||||
function sendEventAndSet(value, setter, settingName) {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
|
|
||||||
const buttonClassName = classNames({
|
|
||||||
'btn-striped-animated': hasChanges,
|
|
||||||
'no-left-radius': true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(PdfCompileButton)
|
|
|
@ -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)
|
|
@ -1,8 +1,11 @@
|
||||||
import { memo, useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
function PdfHybridCodeCheckButton() {
|
function PdfHybridCodeCheckButton() {
|
||||||
const { codeCheckFailed, error, toggleLogs } = useCompileContext()
|
const { codeCheckFailed, error, toggleLogs } = useCompileContext()
|
||||||
|
@ -18,18 +21,24 @@ function PdfHybridCodeCheckButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<OLButton
|
||||||
bsSize="xsmall"
|
variant="danger"
|
||||||
bsStyle="danger"
|
size="sm"
|
||||||
disabled={Boolean(error)}
|
disabled={Boolean(error)}
|
||||||
className="btn-toggle-logs toolbar-item"
|
className="btn-toggle-logs toolbar-item"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
bs3Props={{
|
||||||
|
bsSize: 'xsmall',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="exclamation-triangle" />
|
<BootstrapVersionSwitcher
|
||||||
<span className="toolbar-text toolbar-hide-small">
|
bs3={<Icon type="exclamation-triangle" />}
|
||||||
|
bs5={<MaterialIcon type="warning" />}
|
||||||
|
/>
|
||||||
|
<span className={bsVersion({ bs3: 'toolbar-text' })}>
|
||||||
{t('code_check_failed')}
|
{t('code_check_failed')}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</OLButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
|
||||||
import Icon from '../../../shared/components/icon'
|
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||||
import { useProjectContext } from '../../../shared/context/project-context'
|
import { useProjectContext } from '@/shared/context/project-context'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import { sendMB, isSmallDevice } from '@/infrastructure/event-tracking'
|
||||||
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
import Icon from '@/shared/components/icon'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
function PdfHybridDownloadButton() {
|
function PdfHybridDownloadButton() {
|
||||||
const { pdfDownloadUrl } = useCompileContext()
|
const { pdfDownloadUrl } = useCompileContext()
|
||||||
|
@ -17,8 +18,14 @@ function PdfHybridDownloadButton() {
|
||||||
? t('download_pdf')
|
? t('download_pdf')
|
||||||
: t('please_compile_pdf_before_download')
|
: t('please_compile_pdf_before_download')
|
||||||
|
|
||||||
function handleOnClick() {
|
function handleOnClick(e: React.MouseEvent) {
|
||||||
eventTracking.sendMB('download-pdf-button-click', {
|
const event = e as React.MouseEvent<HTMLAnchorElement>
|
||||||
|
if (event.currentTarget.dataset.disabled === 'true') {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMB('download-pdf-button-click', {
|
||||||
projectId,
|
projectId,
|
||||||
location: 'pdf-preview',
|
location: 'pdf-preview',
|
||||||
isSmallDevice,
|
isSmallDevice,
|
||||||
|
@ -26,16 +33,17 @@ function PdfHybridDownloadButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="download-pdf"
|
id="download-pdf"
|
||||||
description={description}
|
description={description}
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLButton
|
||||||
onClick={handleOnClick}
|
onClick={handleOnClick}
|
||||||
bsStyle="link"
|
variant="link"
|
||||||
className="pdf-toolbar-btn"
|
className="pdf-toolbar-btn"
|
||||||
draggable="false"
|
draggable={false}
|
||||||
|
data-disabled={!pdfDownloadUrl}
|
||||||
disabled={!pdfDownloadUrl}
|
disabled={!pdfDownloadUrl}
|
||||||
download
|
download
|
||||||
href={pdfDownloadUrl || '#'}
|
href={pdfDownloadUrl || '#'}
|
||||||
|
@ -43,9 +51,12 @@ function PdfHybridDownloadButton() {
|
||||||
style={{ pointerEvents: 'auto' }}
|
style={{ pointerEvents: 'auto' }}
|
||||||
aria-label={t('download_pdf')}
|
aria-label={t('download_pdf')}
|
||||||
>
|
>
|
||||||
<Icon type="download" fw />
|
<BootstrapVersionSwitcher
|
||||||
</Button>
|
bs3={<Icon type="download" fw />}
|
||||||
</Tooltip>
|
bs5={<MaterialIcon type="download" />}
|
||||||
|
/>
|
||||||
|
</OLButton>
|
||||||
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { memo, useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Button, Label } from 'react-bootstrap'
|
import Icon from '@/shared/components/icon'
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
import Icon from '../../../shared/components/icon'
|
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLBadge from '@/features/ui/components/ol/ol-badge'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
function PdfHybridLogsButton() {
|
function PdfHybridLogsButton() {
|
||||||
const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } =
|
const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } =
|
||||||
|
@ -25,13 +28,13 @@ function PdfHybridLogsButton() {
|
||||||
const totalCount = errorCount + warningCount
|
const totalCount = errorCount + warningCount
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="logs-toggle"
|
id="logs-toggle"
|
||||||
description={t('logs_and_output_files')}
|
description={t('logs_and_output_files')}
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle="link"
|
variant="link"
|
||||||
disabled={Boolean(error || stoppedOnFirstError)}
|
disabled={Boolean(error || stoppedOnFirstError)}
|
||||||
active={showLogs}
|
active={showLogs}
|
||||||
className="pdf-toolbar-btn toolbar-item log-btn"
|
className="pdf-toolbar-btn toolbar-item log-btn"
|
||||||
|
@ -39,15 +42,18 @@ function PdfHybridLogsButton() {
|
||||||
style={{ position: 'relative' }}
|
style={{ position: 'relative' }}
|
||||||
aria-label={showLogs ? t('view_pdf') : t('view_logs')}
|
aria-label={showLogs ? t('view_pdf') : t('view_logs')}
|
||||||
>
|
>
|
||||||
<Icon type="file-text-o" fw />
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="file-text-o" fw />}
|
||||||
|
bs5={<MaterialIcon type="description" />}
|
||||||
|
/>
|
||||||
|
|
||||||
{!showLogs && totalCount > 0 && (
|
{!showLogs && totalCount > 0 && (
|
||||||
<Label bsStyle={errorCount === 0 ? 'warning' : 'danger'}>
|
<OLBadge bg={errorCount === 0 ? 'warning' : 'danger'}>
|
||||||
{totalCount}
|
{totalCount}
|
||||||
</Label>
|
</OLBadge>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</OLButton>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { memo, useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { buildUrlWithDetachRole } from '../../../shared/utils/url-helper'
|
import { buildUrlWithDetachRole } from '@/shared/utils/url-helper'
|
||||||
import { useLocation } from '../../../shared/hooks/use-location'
|
import { useLocation } from '@/shared/hooks/use-location'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
function PdfOrphanRefreshButton() {
|
function PdfOrphanRefreshButton() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -13,14 +13,9 @@ function PdfOrphanRefreshButton() {
|
||||||
}, [location])
|
}, [location])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<OLButton variant="primary" size="sm" onClick={redirect}>
|
||||||
onClick={redirect}
|
|
||||||
className="btn-orphan"
|
|
||||||
bsStyle="primary"
|
|
||||||
bsSize="small"
|
|
||||||
>
|
|
||||||
{t('redirect_to_editor')}
|
{t('redirect_to_editor')}
|
||||||
</Button>
|
</OLButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ButtonGroup } from 'react-bootstrap'
|
|
||||||
import PDFToolbarButton from './pdf-toolbar-button'
|
import PDFToolbarButton from './pdf-toolbar-button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
|
||||||
|
|
||||||
type PdfPageNumberControlProps = {
|
type PdfPageNumberControlProps = {
|
||||||
setPage: (page: number) => void
|
setPage: (page: number) => void
|
||||||
|
@ -38,7 +38,7 @@ function PdfPageNumberControl({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonGroup className="pdfjs-toolbar-buttons ">
|
<OLButtonGroup className="pdfjs-toolbar-buttons">
|
||||||
<PDFToolbarButton
|
<PDFToolbarButton
|
||||||
tooltipId="pdf-controls-previous-page-tooltip"
|
tooltipId="pdf-controls-previous-page-tooltip"
|
||||||
icon="keyboard_arrow_up"
|
icon="keyboard_arrow_up"
|
||||||
|
@ -53,7 +53,7 @@ function PdfPageNumberControl({
|
||||||
disabled={page === totalPages}
|
disabled={page === totalPages}
|
||||||
onClick={() => setPage(page + 1)}
|
onClick={() => setPage(page + 1)}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</OLButtonGroup>
|
||||||
<div className="pdfjs-page-number-input">
|
<div className="pdfjs-page-number-input">
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { memo, useState, useEffect, useRef } from 'react'
|
import { memo, useState, useEffect, useRef } from 'react'
|
||||||
import { ButtonToolbar } from 'react-bootstrap'
|
import OlButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||||
import PdfCompileButton from './pdf-compile-button'
|
import PdfCompileButton from './pdf-compile-button'
|
||||||
import SwitchToEditorButton from './switch-to-editor-button'
|
import SwitchToEditorButton from './switch-to-editor-button'
|
||||||
import PdfHybridLogsButton from './pdf-hybrid-logs-button'
|
import PdfHybridLogsButton from './pdf-hybrid-logs-button'
|
||||||
|
@ -10,6 +10,8 @@ import PdfHybridCodeCheckButton from './pdf-hybrid-code-check-button'
|
||||||
import PdfOrphanRefreshButton from './pdf-orphan-refresh-button'
|
import PdfOrphanRefreshButton from './pdf-orphan-refresh-button'
|
||||||
import { DetachedSynctexControl } from './detach-synctex-control'
|
import { DetachedSynctexControl } from './detach-synctex-control'
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import { Spinner } from 'react-bootstrap-5'
|
||||||
|
|
||||||
const ORPHAN_UI_TIMEOUT_MS = 5000
|
const ORPHAN_UI_TIMEOUT_MS = 5000
|
||||||
|
|
||||||
|
@ -47,9 +49,9 @@ function PdfPreviewHybridToolbar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
|
<OlButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
|
||||||
{ToolbarInner}
|
{ToolbarInner}
|
||||||
</ButtonToolbar>
|
</OlButtonToolbar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +90,18 @@ function PdfPreviewHybridToolbarConnectingInner() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="toolbar-pdf-orphan">
|
<div className="toolbar-pdf-orphan">
|
||||||
<Icon type="refresh" fw spin />
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="refresh" fw spin />}
|
||||||
|
bs5={
|
||||||
|
<Spinner
|
||||||
|
animation="border"
|
||||||
|
aria-hidden="true"
|
||||||
|
size="sm"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{t('tab_connecting')}…
|
{t('tab_connecting')}…
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -7,8 +7,6 @@ import { getJSON } from '../../../infrastructure/fetch-json'
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import useIsMounted from '../../../shared/hooks/use-is-mounted'
|
import useIsMounted from '../../../shared/hooks/use-is-mounted'
|
||||||
|
@ -21,6 +19,12 @@ import useScopeEventListener from '../../../shared/hooks/use-scope-event-listene
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { Spinner } from 'react-bootstrap-5'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
function GoToCodeButton({
|
function GoToCodeButton({
|
||||||
position,
|
position,
|
||||||
|
@ -30,15 +34,32 @@ function GoToCodeButton({
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const tooltipPlacement = isDetachLayout ? 'bottom' : 'right'
|
const tooltipPlacement = isDetachLayout ? 'bottom' : 'right'
|
||||||
const buttonClasses = classNames('synctex-control', 'btn-secondary', {
|
const buttonClasses = classNames('synctex-control', {
|
||||||
'detach-synctex-control': !!isDetachLayout,
|
'detach-synctex-control': !!isDetachLayout,
|
||||||
})
|
})
|
||||||
|
|
||||||
let buttonIcon = null
|
let buttonIcon = null
|
||||||
if (syncToCodeInFlight) {
|
if (syncToCodeInFlight) {
|
||||||
buttonIcon = <Icon type="refresh" spin className="synctex-spin-icon" />
|
buttonIcon = (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="refresh" spin className="synctex-spin-icon" />}
|
||||||
|
bs5={
|
||||||
|
<Spinner
|
||||||
|
animation="border"
|
||||||
|
aria-hidden="true"
|
||||||
|
size="sm"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else if (!isDetachLayout) {
|
} else if (!isDetachLayout) {
|
||||||
buttonIcon = <Icon type="arrow-left" className="synctex-control-icon" />
|
buttonIcon = (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="arrow-left" className="synctex-control-icon" />}
|
||||||
|
bs5={<MaterialIcon type="arrow_left_alt" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncToCodeWithButton = () => {
|
const syncToCodeWithButton = () => {
|
||||||
|
@ -50,23 +71,26 @@ function GoToCodeButton({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="sync-to-code"
|
id="sync-to-code"
|
||||||
description={t('go_to_pdf_location_in_code')}
|
description={t('go_to_pdf_location_in_code')}
|
||||||
overlayProps={{ placement: tooltipPlacement }}
|
overlayProps={{ placement: tooltipPlacement }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={null}
|
variant="secondary"
|
||||||
bsSize="xs"
|
size="sm"
|
||||||
onClick={syncToCodeWithButton}
|
onClick={syncToCodeWithButton}
|
||||||
disabled={syncToCodeInFlight}
|
disabled={syncToCodeInFlight}
|
||||||
className={buttonClasses}
|
className={buttonClasses}
|
||||||
aria-label={t('go_to_pdf_location_in_code')}
|
aria-label={t('go_to_pdf_location_in_code')}
|
||||||
|
bs3Props={{
|
||||||
|
bsSize: 'xs',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{buttonIcon}
|
{buttonIcon}
|
||||||
{isDetachLayout ? <span> {t('show_in_code')}</span> : ''}
|
{isDetachLayout ? <span> {t('show_in_code')}</span> : ''}
|
||||||
</Button>
|
</OLButton>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,8 +105,7 @@ function GoToPdfButton({
|
||||||
const tooltipPlacement = isDetachLayout ? 'bottom' : 'right'
|
const tooltipPlacement = isDetachLayout ? 'bottom' : 'right'
|
||||||
const buttonClasses = classNames(
|
const buttonClasses = classNames(
|
||||||
'synctex-control',
|
'synctex-control',
|
||||||
'btn-secondary',
|
bsVersion({ bs3: 'toolbar-btn-secondary' }),
|
||||||
'toolbar-btn-secondary',
|
|
||||||
{
|
{
|
||||||
'detach-synctex-control': !!isDetachLayout,
|
'detach-synctex-control': !!isDetachLayout,
|
||||||
}
|
}
|
||||||
|
@ -90,29 +113,49 @@ function GoToPdfButton({
|
||||||
|
|
||||||
let buttonIcon = null
|
let buttonIcon = null
|
||||||
if (syncToPdfInFlight) {
|
if (syncToPdfInFlight) {
|
||||||
buttonIcon = <Icon type="refresh" spin className="synctex-spin-icon" />
|
buttonIcon = (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="refresh" spin className="synctex-spin-icon" />}
|
||||||
|
bs5={
|
||||||
|
<Spinner
|
||||||
|
animation="border"
|
||||||
|
aria-hidden="true"
|
||||||
|
size="sm"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else if (!isDetachLayout) {
|
} else if (!isDetachLayout) {
|
||||||
buttonIcon = <Icon type="arrow-right" className="synctex-control-icon" />
|
buttonIcon = (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="arrow-right" className="synctex-control-icon" />}
|
||||||
|
bs5={<MaterialIcon type="arrow_right_alt" />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="sync-to-pdf"
|
id="sync-to-pdf"
|
||||||
description={t('go_to_code_location_in_pdf')}
|
description={t('go_to_code_location_in_pdf')}
|
||||||
overlayProps={{ placement: tooltipPlacement }}
|
overlayProps={{ placement: tooltipPlacement }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLButton
|
||||||
bsStyle={null}
|
variant="secondary"
|
||||||
bsSize="xs"
|
size="sm"
|
||||||
onClick={() => syncToPdf(cursorPosition)}
|
onClick={() => syncToPdf(cursorPosition)}
|
||||||
disabled={syncToPdfInFlight || !cursorPosition || !hasSingleSelectedDoc}
|
disabled={syncToPdfInFlight || !cursorPosition || !hasSingleSelectedDoc}
|
||||||
className={buttonClasses}
|
className={buttonClasses}
|
||||||
aria-label={t('go_to_code_location_in_pdf')}
|
aria-label={t('go_to_code_location_in_pdf')}
|
||||||
|
bs3Props={{
|
||||||
|
bsSize: 'xs',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{buttonIcon}
|
{buttonIcon}
|
||||||
{isDetachLayout ? <span> {t('show_in_pdf')}</span> : ''}
|
{isDetachLayout ? <span> {t('show_in_pdf')}</span> : ''}
|
||||||
</Button>
|
</OLButton>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Button from 'react-bootstrap/lib/Button'
|
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
import Tooltip from '@/shared/components/tooltip'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
|
||||||
type PDFToolbarButtonProps = {
|
type PDFToolbarButtonProps = {
|
||||||
tooltipId: string
|
tooltipId: string
|
||||||
|
@ -20,7 +20,7 @@ export default function PDFToolbarButton({
|
||||||
shortcut,
|
shortcut,
|
||||||
}: PDFToolbarButtonProps) {
|
}: PDFToolbarButtonProps) {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id={tooltipId}
|
id={tooltipId}
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
|
@ -30,16 +30,15 @@ export default function PDFToolbarButton({
|
||||||
}
|
}
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLButton
|
||||||
aria-label={label}
|
variant="ghost"
|
||||||
bsSize="large"
|
|
||||||
bsStyle={null}
|
|
||||||
className="pdf-toolbar-btn pdfjs-toolbar-button"
|
className="pdf-toolbar-btn pdfjs-toolbar-button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
aria-label={label}
|
||||||
>
|
>
|
||||||
<MaterialIcon type={icon} />
|
<MaterialIcon type={icon} />
|
||||||
</Button>
|
</OLButton>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { useRef } from 'react'
|
||||||
|
|
||||||
import PdfPageNumberControl from './pdf-page-number-control'
|
import PdfPageNumberControl from './pdf-page-number-control'
|
||||||
import PdfZoomButtons from './pdf-zoom-buttons'
|
import PdfZoomButtons from './pdf-zoom-buttons'
|
||||||
import { Button, Overlay, Popover } from 'react-bootstrap'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
import Tooltip from '@/shared/components/tooltip'
|
|
||||||
import useDropdown from '@/shared/hooks/use-dropdown'
|
import useDropdown from '@/shared/hooks/use-dropdown'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
|
||||||
|
import OLPopover from '@/features/ui/components/ol/ol-popover'
|
||||||
|
|
||||||
type PdfViewerControlsMenuButtonProps = {
|
type PdfViewerControlsMenuButtonProps = {
|
||||||
setZoom: (zoom: string) => void
|
setZoom: (zoom: string) => void
|
||||||
|
@ -29,34 +31,37 @@ export default function PdfViewerControlsMenuButton({
|
||||||
ref: popoverRef,
|
ref: popoverRef,
|
||||||
} = useDropdown()
|
} = useDropdown()
|
||||||
|
|
||||||
const targetRef = useRef<any>(null)
|
const targetRef = useRef<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="pdf-controls-menu-tooltip"
|
id="pdf-controls-menu-tooltip"
|
||||||
description={t('view_options')}
|
description={t('view_options')}
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<Button
|
<span>
|
||||||
className="pdf-toolbar-btn pdfjs-toolbar-popover-button"
|
<OLButton
|
||||||
onClick={togglePopover}
|
variant="ghost"
|
||||||
ref={targetRef}
|
className="pdf-toolbar-btn pdfjs-toolbar-popover-button"
|
||||||
>
|
onClick={togglePopover}
|
||||||
<MaterialIcon type="more_horiz" />
|
ref={targetRef}
|
||||||
</Button>
|
>
|
||||||
</Tooltip>
|
<MaterialIcon type="more_horiz" />
|
||||||
|
</OLButton>
|
||||||
|
</span>
|
||||||
|
</OLTooltip>
|
||||||
|
|
||||||
<Overlay
|
<OLOverlay
|
||||||
show={popoverOpen}
|
show={popoverOpen}
|
||||||
target={targetRef.current}
|
target={targetRef.current}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
containerPadding={0}
|
containerPadding={0}
|
||||||
animation
|
transition
|
||||||
rootClose
|
rootClose
|
||||||
onHide={() => togglePopover(false)}
|
onHide={() => togglePopover(false)}
|
||||||
>
|
>
|
||||||
<Popover
|
<OLPopover
|
||||||
className="pdfjs-toolbar-popover"
|
className="pdfjs-toolbar-popover"
|
||||||
id="pdf-toolbar-popover-menu"
|
id="pdf-toolbar-popover-menu"
|
||||||
ref={popoverRef}
|
ref={popoverRef}
|
||||||
|
@ -69,8 +74,8 @@ export default function PdfViewerControlsMenuButton({
|
||||||
<div className="pdfjs-zoom-controls">
|
<div className="pdfjs-zoom-controls">
|
||||||
<PdfZoomButtons setZoom={setZoom} />
|
<PdfZoomButtons setZoom={setZoom} />
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</OLPopover>
|
||||||
</Overlay>
|
</OLOverlay>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ButtonGroup } from 'react-bootstrap'
|
|
||||||
import PDFToolbarButton from './pdf-toolbar-button'
|
import PDFToolbarButton from './pdf-toolbar-button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
|
||||||
|
|
||||||
const isMac = /Mac/.test(window.navigator?.platform)
|
const isMac = /Mac/.test(window.navigator?.platform)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ function PdfZoomButtons({ setZoom }: PdfZoomButtonsProps) {
|
||||||
const zoomOutShortcut = isMac ? '⌘-' : 'Ctrl+-'
|
const zoomOutShortcut = isMac ? '⌘-' : 'Ctrl+-'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup className="pdfjs-toolbar-buttons">
|
<OLButtonGroup className="pdfjs-toolbar-buttons">
|
||||||
<PDFToolbarButton
|
<PDFToolbarButton
|
||||||
tooltipId="pdf-controls-zoom-out-tooltip"
|
tooltipId="pdf-controls-zoom-out-tooltip"
|
||||||
label={t('zoom_out')}
|
label={t('zoom_out')}
|
||||||
|
@ -30,7 +30,7 @@ function PdfZoomButtons({ setZoom }: PdfZoomButtonsProps) {
|
||||||
onClick={() => setZoom('zoom-in')}
|
onClick={() => setZoom('zoom-in')}
|
||||||
shortcut={zoomInShortcut}
|
shortcut={zoomInShortcut}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</OLButtonGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
import { Dropdown, MenuItem } from 'react-bootstrap'
|
import { Dropdown as BS3Dropdown, MenuItem } from 'react-bootstrap'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ControlledDropdown from '@/shared/components/controlled-dropdown'
|
import ControlledDropdown from '@/shared/components/controlled-dropdown'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownDivider,
|
||||||
|
DropdownHeader,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
|
||||||
|
|
||||||
const isMac = /Mac/.test(window.navigator?.platform)
|
const isMac = /Mac/.test(window.navigator?.platform)
|
||||||
|
|
||||||
|
@ -53,93 +63,215 @@ function PdfZoomDropdown({
|
||||||
const showPresentOption = enablePresentationMode && document.fullscreenEnabled
|
const showPresentOption = enablePresentationMode && document.fullscreenEnabled
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlledDropdown
|
<BootstrapVersionSwitcher
|
||||||
id="pdf-zoom-dropdown"
|
bs3={
|
||||||
onSelect={eventKey => {
|
<ControlledDropdown
|
||||||
if (eventKey === 'custom-zoom') {
|
id="pdf-zoom-dropdown"
|
||||||
return
|
onSelect={eventKey => {
|
||||||
}
|
if (eventKey === 'custom-zoom') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (eventKey === 'present') {
|
if (eventKey === 'present') {
|
||||||
requestPresentationMode()
|
requestPresentationMode()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setZoom(eventKey)
|
setZoom(eventKey)
|
||||||
}}
|
}}
|
||||||
pullRight
|
pullRight
|
||||||
>
|
|
||||||
<Dropdown.Toggle
|
|
||||||
bsStyle={null}
|
|
||||||
className="btn pdf-toolbar-btn pdfjs-zoom-dropdown-button small"
|
|
||||||
value={rawScale}
|
|
||||||
title={rawScaleToPercentage(rawScale)}
|
|
||||||
/>
|
|
||||||
<Dropdown.Menu className="pdfjs-zoom-dropdown-menu">
|
|
||||||
<MenuItem
|
|
||||||
draggable={false}
|
|
||||||
disabled
|
|
||||||
className="pdfjs-custom-zoom-menu-item"
|
|
||||||
key="custom-zoom"
|
|
||||||
eventKey="custom-zoom"
|
|
||||||
>
|
>
|
||||||
<input
|
<BS3Dropdown.Toggle
|
||||||
type="text"
|
bsStyle={null}
|
||||||
onFocus={event => event.target.select()}
|
className="btn pdf-toolbar-btn pdfjs-zoom-dropdown-button small"
|
||||||
value={customZoomValue}
|
value={rawScale}
|
||||||
onKeyDown={event => {
|
title={rawScaleToPercentage(rawScale)}
|
||||||
if (event.key === 'Enter') {
|
|
||||||
const zoom = Number(customZoomValue.replace('%', '')) / 100
|
|
||||||
|
|
||||||
// Only allow zoom values between 10% and 999%
|
|
||||||
if (zoom < 0.1) {
|
|
||||||
setZoom('0.1')
|
|
||||||
} else if (zoom > 9.99) {
|
|
||||||
setZoom('9.99')
|
|
||||||
} else {
|
|
||||||
setZoom(`${zoom}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onChange={event => {
|
|
||||||
const rawValue = event.target.value
|
|
||||||
const parsedValue = rawValue.replace(/[^0-9%]/g, '')
|
|
||||||
setCustomZoomValue(parsedValue)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
<BS3Dropdown.Menu className="pdfjs-zoom-dropdown-menu">
|
||||||
<MenuItem divider />
|
<MenuItem
|
||||||
<MenuItem draggable={false} key="zoom-in" eventKey="zoom-in">
|
draggable={false}
|
||||||
<span>{t('zoom_in')}</span>
|
disabled
|
||||||
<Shortcut keys={shortcuts['zoom-in']} />
|
className="pdfjs-custom-zoom-menu-item"
|
||||||
</MenuItem>
|
key="custom-zoom"
|
||||||
<MenuItem draggable={false} key="zoom-out" eventKey="zoom-out">
|
eventKey="custom-zoom"
|
||||||
<span>{t('zoom_out')}</span>
|
>
|
||||||
<Shortcut keys={shortcuts['zoom-out']} />
|
<input
|
||||||
</MenuItem>
|
type="text"
|
||||||
<MenuItem draggable={false} key="page-width" eventKey="page-width">
|
onFocus={event => event.target.select()}
|
||||||
{t('fit_to_width')}
|
value={customZoomValue}
|
||||||
<Shortcut keys={shortcuts['fit-to-width']} />
|
onKeyDown={event => {
|
||||||
</MenuItem>
|
if (event.key === 'Enter') {
|
||||||
<MenuItem draggable={false} key="page-height" eventKey="page-height">
|
const zoom = Number(customZoomValue.replace('%', '')) / 100
|
||||||
{t('fit_to_height')}
|
|
||||||
<Shortcut keys={shortcuts['fit-to-height']} />
|
// Only allow zoom values between 10% and 999%
|
||||||
</MenuItem>
|
if (zoom < 0.1) {
|
||||||
{showPresentOption && <MenuItem divider />}
|
setZoom('0.1')
|
||||||
{showPresentOption && (
|
} else if (zoom > 9.99) {
|
||||||
<MenuItem draggable={false} key="present" eventKey="present">
|
setZoom('9.99')
|
||||||
{t('presentation_mode')}
|
} else {
|
||||||
</MenuItem>
|
setZoom(`${zoom}`)
|
||||||
)}
|
}
|
||||||
<MenuItem divider />
|
}
|
||||||
<MenuItem header>{t('zoom_to')}</MenuItem>
|
}}
|
||||||
{zoomValues.map(value => (
|
onChange={event => {
|
||||||
<MenuItem draggable={false} key={value} eventKey={value}>
|
const rawValue = event.target.value
|
||||||
{rawScaleToPercentage(Number(value))}
|
const parsedValue = rawValue.replace(/[^0-9%]/g, '')
|
||||||
</MenuItem>
|
setCustomZoomValue(parsedValue)
|
||||||
))}
|
}}
|
||||||
</Dropdown.Menu>
|
/>
|
||||||
</ControlledDropdown>
|
</MenuItem>
|
||||||
|
<MenuItem divider />
|
||||||
|
<MenuItem draggable={false} key="zoom-in" eventKey="zoom-in">
|
||||||
|
<span>{t('zoom_in')}</span>
|
||||||
|
<Shortcut keys={shortcuts['zoom-in']} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem draggable={false} key="zoom-out" eventKey="zoom-out">
|
||||||
|
<span>{t('zoom_out')}</span>
|
||||||
|
<Shortcut keys={shortcuts['zoom-out']} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem draggable={false} key="page-width" eventKey="page-width">
|
||||||
|
{t('fit_to_width')}
|
||||||
|
<Shortcut keys={shortcuts['fit-to-width']} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
draggable={false}
|
||||||
|
key="page-height"
|
||||||
|
eventKey="page-height"
|
||||||
|
>
|
||||||
|
{t('fit_to_height')}
|
||||||
|
<Shortcut keys={shortcuts['fit-to-height']} />
|
||||||
|
</MenuItem>
|
||||||
|
{showPresentOption && <MenuItem divider />}
|
||||||
|
{showPresentOption && (
|
||||||
|
<MenuItem draggable={false} key="present" eventKey="present">
|
||||||
|
{t('presentation_mode')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem divider />
|
||||||
|
<MenuItem header>{t('zoom_to')}</MenuItem>
|
||||||
|
{zoomValues.map(value => (
|
||||||
|
<MenuItem draggable={false} key={value} eventKey={value}>
|
||||||
|
{rawScaleToPercentage(Number(value))}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</BS3Dropdown.Menu>
|
||||||
|
</ControlledDropdown>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<Dropdown
|
||||||
|
onSelect={eventKey => {
|
||||||
|
if (eventKey === 'custom-zoom') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventKey === 'present') {
|
||||||
|
requestPresentationMode()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setZoom(eventKey)
|
||||||
|
}}
|
||||||
|
align="end"
|
||||||
|
>
|
||||||
|
<DropdownToggle
|
||||||
|
id="pdf-zoom-dropdown"
|
||||||
|
variant="link"
|
||||||
|
className="pdf-toolbar-btn pdfjs-zoom-dropdown-button"
|
||||||
|
>
|
||||||
|
<small>{rawScaleToPercentage(rawScale)}</small>
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu className="pdfjs-zoom-dropdown-menu">
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
disabled
|
||||||
|
as="div"
|
||||||
|
className="pdfjs-custom-zoom-menu-item"
|
||||||
|
eventKey="custom-zoom"
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
onFocus={event => event.target.select()}
|
||||||
|
value={customZoomValue}
|
||||||
|
onKeyDown={event => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
const zoom =
|
||||||
|
Number(customZoomValue.replace('%', '')) / 100
|
||||||
|
|
||||||
|
// Only allow zoom values between 10% and 999%
|
||||||
|
if (zoom < 0.1) {
|
||||||
|
setZoom('0.1')
|
||||||
|
} else if (zoom > 9.99) {
|
||||||
|
setZoom('9.99')
|
||||||
|
} else {
|
||||||
|
setZoom(`${zoom}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onChange={event => {
|
||||||
|
const rawValue = event.target.value
|
||||||
|
const parsedValue = rawValue.replace(/[^0-9%]/g, '')
|
||||||
|
setCustomZoomValue(parsedValue)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<DropdownDivider />
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
eventKey="zoom-in"
|
||||||
|
trailingIcon={<Shortcut keys={shortcuts['zoom-in']} />}
|
||||||
|
>
|
||||||
|
{t('zoom_in')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
eventKey="zoom-out"
|
||||||
|
trailingIcon={<Shortcut keys={shortcuts['zoom-out']} />}
|
||||||
|
>
|
||||||
|
{t('zoom_out')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
eventKey="page-width"
|
||||||
|
trailingIcon={<Shortcut keys={shortcuts['fit-to-width']} />}
|
||||||
|
>
|
||||||
|
{t('fit_to_width')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem
|
||||||
|
as="button"
|
||||||
|
eventKey="page-height"
|
||||||
|
trailingIcon={<Shortcut keys={shortcuts['fit-to-height']} />}
|
||||||
|
>
|
||||||
|
{t('fit_to_height')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
{showPresentOption && <DropdownDivider />}
|
||||||
|
{showPresentOption && (
|
||||||
|
<li role="none">
|
||||||
|
<DropdownItem as="button" eventKey="present">
|
||||||
|
{t('presentation_mode')}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
<DropdownDivider />
|
||||||
|
<DropdownHeader aria-hidden="true">{t('zoom_to')}</DropdownHeader>
|
||||||
|
{zoomValues.map(value => (
|
||||||
|
<li role="none" key={value}>
|
||||||
|
<DropdownItem as="button" eventKey={value}>
|
||||||
|
{rawScaleToPercentage(Number(value))}
|
||||||
|
</DropdownItem>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ function SwitchToEditorButton() {
|
||||||
return (
|
return (
|
||||||
<OLButton
|
<OLButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="sm"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
bs3Props={{
|
bs3Props={{
|
||||||
bsSize: 'xsmall',
|
bsSize: 'xsmall',
|
||||||
|
|
|
@ -12,7 +12,7 @@ import DeleteLeaveProjectsButton from './buttons/delete-leave-projects-button'
|
||||||
import LeaveProjectsButton from './buttons/leave-projects-button'
|
import LeaveProjectsButton from './buttons/leave-projects-button'
|
||||||
import DeleteProjectsButton from './buttons/delete-projects-button'
|
import DeleteProjectsButton from './buttons/delete-projects-button'
|
||||||
import OLButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
|
import OLButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
|
||||||
import OlButtonGroup from '@/features/ui/components/ol/ol-button-group'
|
import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
|
||||||
|
|
||||||
function ProjectTools() {
|
function ProjectTools() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -20,27 +20,27 @@ function ProjectTools() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OLButtonToolbar aria-label={t('toolbar_selected_projects')}>
|
<OLButtonToolbar aria-label={t('toolbar_selected_projects')}>
|
||||||
<OlButtonGroup
|
<OLButtonGroup
|
||||||
aria-label={t('toolbar_selected_projects_management_actions')}
|
aria-label={t('toolbar_selected_projects_management_actions')}
|
||||||
>
|
>
|
||||||
<DownloadProjectsButton />
|
<DownloadProjectsButton />
|
||||||
{filter !== 'archived' && <ArchiveProjectsButton />}
|
{filter !== 'archived' && <ArchiveProjectsButton />}
|
||||||
{filter !== 'trashed' && <TrashProjectsButton />}
|
{filter !== 'trashed' && <TrashProjectsButton />}
|
||||||
</OlButtonGroup>
|
</OLButtonGroup>
|
||||||
|
|
||||||
{(filter === 'trashed' || filter === 'archived') && (
|
{(filter === 'trashed' || filter === 'archived') && (
|
||||||
<OlButtonGroup aria-label={t('toolbar_selected_projects_restore')}>
|
<OLButtonGroup aria-label={t('toolbar_selected_projects_restore')}>
|
||||||
{filter === 'trashed' && <UntrashProjectsButton />}
|
{filter === 'trashed' && <UntrashProjectsButton />}
|
||||||
{filter === 'archived' && <UnarchiveProjectsButton />}
|
{filter === 'archived' && <UnarchiveProjectsButton />}
|
||||||
</OlButtonGroup>
|
</OLButtonGroup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{filter === 'trashed' && (
|
{filter === 'trashed' && (
|
||||||
<OlButtonGroup aria-label={t('toolbar_selected_projects_remove')}>
|
<OLButtonGroup aria-label={t('toolbar_selected_projects_remove')}>
|
||||||
<LeaveProjectsButton />
|
<LeaveProjectsButton />
|
||||||
<DeleteProjectsButton />
|
<DeleteProjectsButton />
|
||||||
<DeleteLeaveProjectsButton />
|
<DeleteLeaveProjectsButton />
|
||||||
</OlButtonGroup>
|
</OLButtonGroup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!['archived', 'trashed'].includes(filter) && <TagsDropdown />}
|
{!['archived', 'trashed'].includes(filter) && <TagsDropdown />}
|
||||||
|
|
|
@ -8,7 +8,7 @@ function PrimaryButton({
|
||||||
}: OLButtonProps) {
|
}: OLButtonProps) {
|
||||||
return (
|
return (
|
||||||
<OLButton
|
<OLButton
|
||||||
size="small"
|
size="sm"
|
||||||
disabled={disabled && !isLoading}
|
disabled={disabled && !isLoading}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|
|
@ -23,7 +23,7 @@ function DeleteButton({ disabled, isLoading, onClick }: DeleteButtonProps) {
|
||||||
variant="danger"
|
variant="danger"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
size="small"
|
size="sm"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
accessibilityLabel={t('remove') || ''}
|
accessibilityLabel={t('remove') || ''}
|
||||||
icon={
|
icon={
|
||||||
|
|
|
@ -56,7 +56,7 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
|
||||||
<OLButton
|
<OLButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="btn-link-accounts"
|
className="btn-link-accounts"
|
||||||
size="small"
|
size="sm"
|
||||||
disabled={linkAccountsButtonDisabled}
|
disabled={linkAccountsButtonDisabled}
|
||||||
onClick={handleLinkAccountsButtonClick}
|
onClick={handleLinkAccountsButtonClick}
|
||||||
>
|
>
|
||||||
|
|
|
@ -152,7 +152,7 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
|
||||||
className="btn-link-accounts"
|
className="btn-link-accounts"
|
||||||
disabled={linkAccountsButtonDisabled}
|
disabled={linkAccountsButtonDisabled}
|
||||||
onClick={handleLinkAccountsButtonClick}
|
onClick={handleLinkAccountsButtonClick}
|
||||||
size="small"
|
size="sm"
|
||||||
>
|
>
|
||||||
{t('link_accounts')}
|
{t('link_accounts')}
|
||||||
</OLButton>
|
</OLButton>
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default function AccessLevelsChanged({
|
||||||
<div className="upgrade-actions">
|
<div className="upgrade-actions">
|
||||||
{user.allowedFreeTrial ? (
|
{user.allowedFreeTrial ? (
|
||||||
<StartFreeTrialButton
|
<StartFreeTrialButton
|
||||||
buttonProps={{ variant: 'secondary', size: 'small' }}
|
buttonProps={{ variant: 'secondary', size: 'sm' }}
|
||||||
source="project-sharing"
|
source="project-sharing"
|
||||||
variant="exceeds"
|
variant="exceeds"
|
||||||
>
|
>
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default function AddCollaboratorsUpgrade() {
|
||||||
<div className="upgrade-actions">
|
<div className="upgrade-actions">
|
||||||
{user.allowedFreeTrial ? (
|
{user.allowedFreeTrial ? (
|
||||||
<StartFreeTrialButton
|
<StartFreeTrialButton
|
||||||
buttonProps={{ variant: 'secondary', size: 'small' }}
|
buttonProps={{ variant: 'secondary', size: 'sm' }}
|
||||||
source="project-sharing"
|
source="project-sharing"
|
||||||
variant="exceeds"
|
variant="exceeds"
|
||||||
>
|
>
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default function CollaboratorsLimitUpgrade() {
|
||||||
action={
|
action={
|
||||||
user.allowedFreeTrial ? (
|
user.allowedFreeTrial ? (
|
||||||
<StartFreeTrialButton
|
<StartFreeTrialButton
|
||||||
buttonProps={{ variant: 'premium', size: 'default' }}
|
buttonProps={{ variant: 'premium' }}
|
||||||
source="project-sharing"
|
source="project-sharing"
|
||||||
variant="limit"
|
variant="limit"
|
||||||
>
|
>
|
||||||
|
@ -71,7 +71,7 @@ export default function CollaboratorsLimitUpgrade() {
|
||||||
action={
|
action={
|
||||||
user.allowedFreeTrial ? (
|
user.allowedFreeTrial ? (
|
||||||
<StartFreeTrialButton
|
<StartFreeTrialButton
|
||||||
buttonProps={{ variant: 'secondary', size: 'small' }}
|
buttonProps={{ variant: 'secondary', size: 'sm' }}
|
||||||
source="project-sharing"
|
source="project-sharing"
|
||||||
variant="limit"
|
variant="limit"
|
||||||
>
|
>
|
||||||
|
|
|
@ -25,7 +25,7 @@ function SwitchToPDFButton() {
|
||||||
return (
|
return (
|
||||||
<OLButton
|
<OLButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="sm"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
bs3Props={{
|
bs3Props={{
|
||||||
bsSize: 'xsmall',
|
bsSize: 'xsmall',
|
||||||
|
|
|
@ -345,7 +345,7 @@ export function ChangeToGroupModal() {
|
||||||
)}
|
)}
|
||||||
<OLButton
|
<OLButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="large"
|
size="lg"
|
||||||
disabled={
|
disabled={
|
||||||
queryingGroupPlanToChangeToPrice ||
|
queryingGroupPlanToChangeToPrice ||
|
||||||
!groupPlanToChangeToPrice ||
|
!groupPlanToChangeToPrice ||
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { Badge as BSBadge } from 'react-bootstrap-5'
|
import { Badge as BSBadge, BadgeProps as BSBadgeProps } from 'react-bootstrap-5'
|
||||||
import { MergeAndOverride } from '../../../../../../types/utils'
|
import { MergeAndOverride } from '../../../../../../types/utils'
|
||||||
|
|
||||||
type BadgeProps = MergeAndOverride<
|
type BadgeProps = MergeAndOverride<
|
||||||
React.ComponentProps<typeof BSBadge>,
|
BSBadgeProps,
|
||||||
{
|
{
|
||||||
prepend?: React.ReactNode
|
prepend?: React.ReactNode
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
function Badge({ prepend, children, closeBtnProps, ...rest }: BadgeProps) {
|
function Badge({ prepend, children, ...rest }: BadgeProps) {
|
||||||
return (
|
return (
|
||||||
<BSBadge {...rest}>
|
<BSBadge {...rest}>
|
||||||
{prepend && <span className="badge-prepend">{prepend}</span>}
|
{prepend && <span className="badge-prepend">{prepend}</span>}
|
||||||
|
|
|
@ -5,12 +5,6 @@ import classNames from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
|
||||||
const sizeClasses = new Map<ButtonProps['size'], string>([
|
|
||||||
['small', 'btn-sm'],
|
|
||||||
['default', ''],
|
|
||||||
['large', 'btn-lg'],
|
|
||||||
])
|
|
||||||
|
|
||||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
@ -19,7 +13,6 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
leadingIcon,
|
leadingIcon,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
loadingLabel,
|
loadingLabel,
|
||||||
size = 'default',
|
|
||||||
trailingIcon,
|
trailingIcon,
|
||||||
variant = 'primary',
|
variant = 'primary',
|
||||||
...props
|
...props
|
||||||
|
@ -28,13 +21,28 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const sizeClass = sizeClasses.get(size)
|
const buttonClassName = classNames('d-inline-grid', className, {
|
||||||
const buttonClassName = classNames('d-inline-grid', sizeClass, className, {
|
|
||||||
'button-loading': isLoading,
|
'button-loading': isLoading,
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadingSpinnerClassName =
|
const loadingSpinnerClassName =
|
||||||
size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
|
props.size === 'lg' ? 'loading-spinner-large' : 'loading-spinner-small'
|
||||||
const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
|
const materialIconClassName =
|
||||||
|
props.size === 'lg' ? 'icon-large' : 'icon-small'
|
||||||
|
|
||||||
|
const leadingIconComponent =
|
||||||
|
leadingIcon && typeof leadingIcon === 'string' ? (
|
||||||
|
<MaterialIcon type={leadingIcon} className={materialIconClassName} />
|
||||||
|
) : (
|
||||||
|
leadingIcon
|
||||||
|
)
|
||||||
|
|
||||||
|
const trailingIconComponent =
|
||||||
|
trailingIcon && typeof trailingIcon === 'string' ? (
|
||||||
|
<MaterialIcon type={trailingIcon} className={materialIconClassName} />
|
||||||
|
) : (
|
||||||
|
trailingIcon
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BS5Button
|
<BS5Button
|
||||||
|
@ -58,19 +66,9 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="button-content" aria-hidden={isLoading}>
|
<span className="button-content" aria-hidden={isLoading}>
|
||||||
{leadingIcon && (
|
{leadingIconComponent}
|
||||||
<MaterialIcon
|
|
||||||
type={leadingIcon}
|
|
||||||
className={materialIconClassName}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
{trailingIcon && (
|
{trailingIconComponent}
|
||||||
<MaterialIcon
|
|
||||||
type={trailingIcon}
|
|
||||||
className={materialIconClassName}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</BS5Button>
|
</BS5Button>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import classNames from 'classnames'
|
|
||||||
import Button from './button'
|
|
||||||
import {
|
|
||||||
Dropdown,
|
|
||||||
DropdownItem,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownToggle,
|
|
||||||
} from './dropdown-menu'
|
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
|
||||||
import type { SplitButtonProps } from '@/features/ui/components/types/split-button-props'
|
|
||||||
|
|
||||||
export function SplitButton({
|
|
||||||
accessibilityLabel,
|
|
||||||
align,
|
|
||||||
id,
|
|
||||||
items,
|
|
||||||
text,
|
|
||||||
variant,
|
|
||||||
...props
|
|
||||||
}: SplitButtonProps) {
|
|
||||||
const buttonClassName = classNames('split-button')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Dropdown align={align}>
|
|
||||||
<Button className={buttonClassName} variant={variant} {...props}>
|
|
||||||
<span className="split-button-content">{text}</span>
|
|
||||||
</Button>
|
|
||||||
<DropdownToggle
|
|
||||||
bsPrefix="dropdown-button-toggle"
|
|
||||||
id={id}
|
|
||||||
variant={variant}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<MaterialIcon
|
|
||||||
className="split-button-caret"
|
|
||||||
type="expand_more"
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
|
||||||
/>
|
|
||||||
</DropdownToggle>
|
|
||||||
<DropdownMenu>
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<li key={index}>
|
|
||||||
<DropdownItem eventKey={item.eventKey}>{item.label}</DropdownItem>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</DropdownMenu>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ type OLButtonGroupProps = ButtonGroupProps & {
|
||||||
bs3Props?: Record<string, unknown>
|
bs3Props?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
function OlButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
|
function OLButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
|
||||||
const bs3ButtonGroupProps: BS3ButtonGroupProps = {
|
const bs3ButtonGroupProps: BS3ButtonGroupProps = {
|
||||||
children: rest.children,
|
children: rest.children,
|
||||||
className: rest.className,
|
className: rest.className,
|
||||||
|
@ -27,4 +27,4 @@ function OlButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OlButtonGroup
|
export default OLButtonGroup
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
|
import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
|
||||||
import { Button as BS3Button } from 'react-bootstrap'
|
import { Button as BS3Button } from 'react-bootstrap'
|
||||||
import type { ButtonProps } from '@/features/ui/components/types/button-props'
|
import type { ButtonProps } from '@/features/ui/components/types/button-props'
|
||||||
|
@ -5,7 +6,7 @@ import type { ButtonProps as BS3ButtonPropsBase } from 'react-bootstrap'
|
||||||
import Button from '../bootstrap-5/button'
|
import Button from '../bootstrap-5/button'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
||||||
|
import { callFnsInSequence } from '@/utils/functions'
|
||||||
export type BS3ButtonSize = 'xsmall' | 'sm' | 'medium' | 'lg'
|
export type BS3ButtonSize = 'xsmall' | 'sm' | 'medium' | 'lg'
|
||||||
|
|
||||||
export type OLButtonProps = ButtonProps & {
|
export type OLButtonProps = ButtonProps & {
|
||||||
|
@ -14,6 +15,10 @@ export type OLButtonProps = ButtonProps & {
|
||||||
bsSize?: BS3ButtonSize
|
bsSize?: BS3ButtonSize
|
||||||
block?: boolean
|
block?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
|
onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
onFocus?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
|
onBlur?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +30,7 @@ export type BS3ButtonProps = Omit<BS3ButtonPropsBase, 'onClick'> & {
|
||||||
export function bs3ButtonProps(props: ButtonProps) {
|
export function bs3ButtonProps(props: ButtonProps) {
|
||||||
const bs3ButtonProps: BS3ButtonProps = {
|
const bs3ButtonProps: BS3ButtonProps = {
|
||||||
bsStyle: null,
|
bsStyle: null,
|
||||||
bsSize: mapBsButtonSizes(props.size),
|
bsSize: props.size,
|
||||||
className: classnames(`btn-${props.variant || 'primary'}`, props.className),
|
className: classnames(`btn-${props.variant || 'primary'}`, props.className),
|
||||||
disabled: props.isLoading || props.disabled,
|
disabled: props.isLoading || props.disabled,
|
||||||
form: props.form,
|
form: props.form,
|
||||||
|
@ -34,38 +39,51 @@ export function bs3ButtonProps(props: ButtonProps) {
|
||||||
rel: props.rel,
|
rel: props.rel,
|
||||||
onClick: props.onClick,
|
onClick: props.onClick,
|
||||||
type: props.type,
|
type: props.type,
|
||||||
|
draggable: props.draggable,
|
||||||
|
download: props.download,
|
||||||
|
style: props.style,
|
||||||
|
active: props.active,
|
||||||
}
|
}
|
||||||
return bs3ButtonProps
|
return bs3ButtonProps
|
||||||
}
|
}
|
||||||
|
|
||||||
// maps Bootstrap 5 sizes to Bootstrap 3 sizes
|
const OLButton = forwardRef<HTMLButtonElement, OLButtonProps>(
|
||||||
export const mapBsButtonSizes = (
|
({ bs3Props = {}, ...rest }, ref) => {
|
||||||
size: ButtonProps['size']
|
const { className: _, ...restBs3Props } = bs3Props
|
||||||
): 'sm' | 'lg' | undefined =>
|
|
||||||
size === 'small' ? 'sm' : size === 'large' ? 'lg' : undefined
|
|
||||||
|
|
||||||
export default function OLButton({ bs3Props = {}, ...rest }: OLButtonProps) {
|
// BS3 OverlayTrigger automatically provides 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' event handlers
|
||||||
const { className: _, ...restBs3Props } = bs3Props
|
const bs3FinalProps = {
|
||||||
|
...restBs3Props,
|
||||||
|
onMouseOver: callFnsInSequence(bs3Props?.onMouseOver, rest.onMouseOver),
|
||||||
|
onMouseOut: callFnsInSequence(bs3Props?.onMouseOut, rest.onMouseOut),
|
||||||
|
onFocus: callFnsInSequence(bs3Props?.onFocus, rest.onFocus),
|
||||||
|
onBlur: callFnsInSequence(bs3Props?.onBlur, rest.onBlur),
|
||||||
|
}
|
||||||
|
|
||||||
// Get all `aria-*` and `data-*` attributes
|
// Get all `aria-*` and `data-*` attributes
|
||||||
const extraProps = getAriaAndDataProps(rest)
|
const extraProps = getAriaAndDataProps(rest)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BootstrapVersionSwitcher
|
<BootstrapVersionSwitcher
|
||||||
bs3={
|
bs3={
|
||||||
<BS3Button
|
<BS3Button
|
||||||
{...bs3ButtonProps({
|
{...bs3ButtonProps({
|
||||||
...rest,
|
...rest,
|
||||||
// Override the `className` with bs3 specific className (if provided)
|
// Override the `className` with bs3 specific className (if provided)
|
||||||
className: bs3Props?.className ?? rest.className,
|
className: bs3Props?.className ?? rest.className,
|
||||||
})}
|
})}
|
||||||
{...restBs3Props}
|
{...extraProps}
|
||||||
{...extraProps}
|
{...bs3FinalProps}
|
||||||
>
|
ref={ref as React.LegacyRef<any> | undefined}
|
||||||
{bs3Props?.loading || rest.children}
|
>
|
||||||
</BS3Button>
|
{bs3Props?.loading || rest.children}
|
||||||
}
|
</BS3Button>
|
||||||
bs5={<Button {...rest} />}
|
}
|
||||||
/>
|
bs5={<Button {...rest} ref={ref} />}
|
||||||
)
|
/>
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
OLButton.displayName = 'OLButton'
|
||||||
|
|
||||||
|
export default OLButton
|
||||||
|
|
|
@ -9,10 +9,13 @@ type OLTooltipProps = React.ComponentProps<typeof Tooltip> & {
|
||||||
function OLTooltip(props: OLTooltipProps) {
|
function OLTooltip(props: OLTooltipProps) {
|
||||||
const { bs3Props, ...bs5Props } = props
|
const { bs3Props, ...bs5Props } = props
|
||||||
|
|
||||||
const bs3TooltipProps: React.ComponentProps<typeof BS3Tooltip> = {
|
type BS3TooltipProps = React.ComponentProps<typeof BS3Tooltip>
|
||||||
|
|
||||||
|
const bs3TooltipProps: BS3TooltipProps = {
|
||||||
children: bs5Props.children,
|
children: bs5Props.children,
|
||||||
id: bs5Props.id,
|
id: bs5Props.id,
|
||||||
description: bs5Props.description,
|
description: bs5Props.description,
|
||||||
|
tooltipProps: bs5Props.tooltipProps as BS3TooltipProps,
|
||||||
overlayProps: {
|
overlayProps: {
|
||||||
placement: bs5Props.overlayProps?.placement,
|
placement: bs5Props.overlayProps?.placement,
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,8 +4,10 @@ export type ButtonProps = {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
download?: boolean
|
||||||
|
draggable?: boolean
|
||||||
form?: string
|
form?: string
|
||||||
leadingIcon?: string
|
leadingIcon?: string | React.ReactNode
|
||||||
href?: string
|
href?: string
|
||||||
target?: string
|
target?: string
|
||||||
rel?: string
|
rel?: string
|
||||||
|
@ -16,8 +18,10 @@ export type ButtonProps = {
|
||||||
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
|
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
onFocus?: React.FocusEventHandler<HTMLButtonElement>
|
onFocus?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
onBlur?: React.FocusEventHandler<HTMLButtonElement>
|
onBlur?: React.FocusEventHandler<HTMLButtonElement>
|
||||||
size?: 'small' | 'default' | 'large'
|
size?: 'sm' | 'lg' | undefined
|
||||||
trailingIcon?: string
|
style?: Record<PropertyKey, string>
|
||||||
|
active?: boolean
|
||||||
|
trailingIcon?: string | React.ReactNode
|
||||||
type?: 'button' | 'reset' | 'submit'
|
type?: 'button' | 'reset' | 'submit'
|
||||||
variant?:
|
variant?:
|
||||||
| 'primary'
|
| 'primary'
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import type { ElementType, ReactNode, PropsWithChildren } from 'react'
|
import type { ElementType, ReactNode, PropsWithChildren } from 'react'
|
||||||
import type { SplitButtonVariants } from './split-button-props'
|
import type { ButtonProps } from '@/features/ui/components/types/button-props'
|
||||||
|
|
||||||
|
type SplitButtonVariants = Extract<
|
||||||
|
ButtonProps['variant'],
|
||||||
|
'primary' | 'secondary' | 'danger' | 'link'
|
||||||
|
>
|
||||||
|
|
||||||
export type DropdownProps = {
|
export type DropdownProps = {
|
||||||
align?:
|
align?:
|
||||||
|
@ -47,7 +52,7 @@ export type DropdownToggleProps = PropsWithChildren<{
|
||||||
id?: string // necessary for assistive technologies
|
id?: string // necessary for assistive technologies
|
||||||
variant?: SplitButtonVariants
|
variant?: SplitButtonVariants
|
||||||
as?: ElementType
|
as?: ElementType
|
||||||
size?: 'sm' | 'lg'
|
size?: 'sm' | 'lg' | undefined
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type DropdownMenuProps = PropsWithChildren<{
|
export type DropdownMenuProps = PropsWithChildren<{
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { PropsWithChildren } from 'react'
|
|
||||||
import type {
|
|
||||||
DropdownItemProps,
|
|
||||||
DropdownProps,
|
|
||||||
DropdownToggleProps,
|
|
||||||
} from './dropdown-menu-props'
|
|
||||||
import type { ButtonProps } from './button-props'
|
|
||||||
|
|
||||||
type SplitButtonItemProps = Pick<
|
|
||||||
DropdownItemProps,
|
|
||||||
'eventKey' | 'leadingIcon'
|
|
||||||
> & {
|
|
||||||
label: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SplitButtonVariants = Extract<
|
|
||||||
ButtonProps['variant'],
|
|
||||||
'primary' | 'secondary' | 'danger' | 'link'
|
|
||||||
>
|
|
||||||
|
|
||||||
export type SplitButtonProps = PropsWithChildren<{
|
|
||||||
accessibilityLabel?: string
|
|
||||||
align?: DropdownProps['align']
|
|
||||||
disabled?: boolean
|
|
||||||
id: DropdownToggleProps['id']
|
|
||||||
items: SplitButtonItemProps[]
|
|
||||||
text: string
|
|
||||||
variant: SplitButtonVariants
|
|
||||||
}>
|
|
|
@ -53,7 +53,7 @@ const TextButton: FC<{
|
||||||
return (
|
return (
|
||||||
<OLButton
|
<OLButton
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
size="small"
|
size="sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="copy-button"
|
className="copy-button"
|
||||||
bs3Props={{ bsSize: 'xsmall' }}
|
bs3Props={{ bsSize: 'xsmall' }}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default function useDropdown(defaultOpen = false) {
|
||||||
|
|
||||||
// handle dropdown toggle
|
// handle dropdown toggle
|
||||||
const handleToggle = useCallback(value => {
|
const handleToggle = useCallback(value => {
|
||||||
setOpen(value)
|
setOpen(Boolean(value))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// close the dropdown on click outside the dropdown
|
// close the dropdown on click outside the dropdown
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const ButtonStyle = args => {
|
||||||
{...args}
|
{...args}
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
size: 'large',
|
size: 'lg',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,11 +27,6 @@ const meta: Meta<typeof Badge> = {
|
||||||
disable: true,
|
disable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
closeBtnProps: {
|
|
||||||
table: {
|
|
||||||
disable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
export default meta
|
export default meta
|
||||||
|
|
|
@ -1,33 +1,60 @@
|
||||||
import { SplitButton } from '@/features/ui/components/bootstrap-5/split-button'
|
import { Fragment } from 'react'
|
||||||
import type { Meta } from '@storybook/react'
|
import type { Meta } from '@storybook/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownHeader,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
|
import Button from '@/features/ui/components/bootstrap-5/button'
|
||||||
|
import { ButtonGroup } from 'react-bootstrap-5'
|
||||||
|
|
||||||
type Args = React.ComponentProps<typeof SplitButton>
|
type Args = React.ComponentProps<typeof Dropdown>
|
||||||
|
|
||||||
export const Dropdown = (args: Args) => {
|
export const Sizes = (args: Args) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const sizes = {
|
||||||
|
Large: 'lg',
|
||||||
|
Regular: undefined,
|
||||||
|
Small: 'sm',
|
||||||
|
} as const
|
||||||
|
const variants = ['primary', 'secondary', 'danger'] as const
|
||||||
|
|
||||||
return <SplitButton accessibilityLabel={t('expand')} {...args} />
|
return Object.entries(sizes).map(([label, size]) => (
|
||||||
|
<Fragment key={`${label}-${size}`}>
|
||||||
|
<h4>{label}</h4>
|
||||||
|
<div style={{ display: 'inline-flex', gap: '10px' }}>
|
||||||
|
{variants.map(variant => (
|
||||||
|
<Dropdown key={variant} as={ButtonGroup}>
|
||||||
|
<Button variant={variant} size={size}>
|
||||||
|
Split Button
|
||||||
|
</Button>
|
||||||
|
<DropdownToggle
|
||||||
|
split
|
||||||
|
variant={variant}
|
||||||
|
id={`split-btn-${variant}-${size}`}
|
||||||
|
size={size}
|
||||||
|
aria-label={t('expand')}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownHeader>Header</DropdownHeader>
|
||||||
|
<DropdownItem as="button">Action 1</DropdownItem>
|
||||||
|
<DropdownItem as="button">Action 2</DropdownItem>
|
||||||
|
<DropdownItem as="button">Action 3</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
const meta: Meta<typeof SplitButton> = {
|
const meta: Meta<typeof Dropdown> = {
|
||||||
title: 'Shared/Components/Bootstrap 5/SplitButton',
|
title: 'Shared/Components/Bootstrap 5/SplitButton',
|
||||||
component: SplitButton,
|
component: Dropdown,
|
||||||
args: {
|
args: {
|
||||||
align: { sm: 'start' },
|
align: { sm: 'start' },
|
||||||
id: 'split-button',
|
|
||||||
items: [
|
|
||||||
{ eventKey: '1', label: 'Action 1' },
|
|
||||||
{ eventKey: '2', label: 'Action 2' },
|
|
||||||
{ eventKey: '3', label: 'Action 3' },
|
|
||||||
],
|
|
||||||
text: 'Split Button',
|
|
||||||
},
|
|
||||||
argTypes: {
|
|
||||||
id: {
|
|
||||||
table: {
|
|
||||||
disable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
bootstrap5: true,
|
bootstrap5: true,
|
||||||
|
|
|
@ -53,9 +53,6 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.btn-toggle-logs-label {
|
|
||||||
padding-left: @line-height-computed / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-toolbar-btn {
|
.pdf-toolbar-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -64,7 +61,6 @@
|
||||||
padding: 4px 2px;
|
padding: 4px 2px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 2px;
|
|
||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|
|
@ -67,11 +67,11 @@
|
||||||
|
|
||||||
// Toolbar
|
// Toolbar
|
||||||
@mixin toolbar-sm-height {
|
@mixin toolbar-sm-height {
|
||||||
height: 32px;
|
height: var(--toolbar-small-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin toolbar-alt-bg() {
|
@mixin toolbar-alt-bg() {
|
||||||
background-color: var(--bg-dark-secondary);
|
background-color: var(--toolbar-alt-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin theme($name) {
|
@mixin theme($name) {
|
||||||
|
@ -87,3 +87,20 @@
|
||||||
@mixin box-shadow-button-input {
|
@mixin box-shadow-button-input {
|
||||||
box-shadow: 0 0 0 2px var(--blue-30);
|
box-shadow: 0 0 0 2px var(--blue-30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin animation($animation) {
|
||||||
|
animation: $animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin striped($color: rgba(255, 255, 255, 0.15), $angle: 45deg) {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
$angle,
|
||||||
|
$color 25%,
|
||||||
|
transparent 25%,
|
||||||
|
transparent 50%,
|
||||||
|
$color 50%,
|
||||||
|
$color 75%,
|
||||||
|
transparent 75%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
@import 'button-group';
|
@import 'button-group';
|
||||||
@import 'dropdown-menu';
|
@import 'dropdown-menu';
|
||||||
@import 'image';
|
@import 'image';
|
||||||
@import 'split-button';
|
|
||||||
@import 'notifications';
|
@import 'notifications';
|
||||||
@import 'system-messages';
|
@import 'system-messages';
|
||||||
@import 'tooltip';
|
@import 'tooltip';
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.btn-group {
|
.btn-group {
|
||||||
> .btn {
|
> .btn {
|
||||||
&:first-child {
|
&:first-of-type {
|
||||||
padding-left: var(--spacing-05);
|
padding-left: var(--spacing-05);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-of-type {
|
||||||
padding-right: var(--spacing-05);
|
padding-right: var(--spacing-05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,7 @@
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
padding-right: var(--spacing-05);
|
padding-right: var(--spacing-05);
|
||||||
padding-left: var(--spacing-05);
|
padding-left: var(--spacing-05);
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
&.btn-primary,
|
&.btn-primary,
|
||||||
&.btn-danger {
|
&.btn-danger {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
.split-button {
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.split-button-caret {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
|
@ -12,5 +12,7 @@
|
||||||
@import 'editor/file-tree';
|
@import 'editor/file-tree';
|
||||||
@import 'editor/figure-modal';
|
@import 'editor/figure-modal';
|
||||||
@import 'subscription';
|
@import 'subscription';
|
||||||
|
@import 'editor/pdf';
|
||||||
|
@import 'editor/compile-button';
|
||||||
@import 'website-redesign';
|
@import 'website-redesign';
|
||||||
@import 'group-settings';
|
@import 'group-settings';
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
$stripe-width: 20px;
|
||||||
|
|
||||||
|
@keyframes pdf-toolbar-stripes {
|
||||||
|
from {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
background-position: $stripe-width 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-striped-animated {
|
||||||
|
@include striped;
|
||||||
|
|
||||||
|
background-size: $stripe-width $stripe-width;
|
||||||
|
background-origin: content-box;
|
||||||
|
|
||||||
|
@include animation(pdf-toolbar-stripes 2s linear infinite);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detach-compile-button {
|
||||||
|
&[disabled],
|
||||||
|
&[disabled]:active {
|
||||||
|
background-color: var(--bs-btn-bg);
|
||||||
|
color: var(--bs-btn-color);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,357 @@
|
||||||
|
:root {
|
||||||
|
--pdf-bg: var(--neutral-10);
|
||||||
|
--pdf-toolbar-btn-hover-color: rgb(125 125 125 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include theme('light') {
|
||||||
|
--pdf-toolbar-btn-hover-color: var(--neutral-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf .toolbar.toolbar-pdf {
|
||||||
|
@include toolbar-sm-height;
|
||||||
|
@include toolbar-alt-bg;
|
||||||
|
|
||||||
|
padding-right: var(--spacing-03);
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
.btn.disabled,
|
||||||
|
.btn[disabled] {
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-pdf-left {
|
||||||
|
gap: var(--spacing-02);
|
||||||
|
|
||||||
|
.dropdown > .btn {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
|
||||||
|
&[disabled],
|
||||||
|
&[disabled]:active {
|
||||||
|
background-color: var(--bs-btn-bg);
|
||||||
|
color: var(--bs-btn-color);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-pdf-orphan,
|
||||||
|
.toolbar-pdf-left,
|
||||||
|
.toolbar-pdf-right,
|
||||||
|
.toolbar-pdf-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-pdf-orphan,
|
||||||
|
.toolbar-pdf-controls {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-pdf-controls {
|
||||||
|
margin-right: var(--spacing-02);
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-pdf-right {
|
||||||
|
flex: 1;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-pdf-orphan {
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--toolbar-btn-color);
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-left: var(--spacing-03);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.pdf-toolbar-btn {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--toolbar-btn-color);
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0 var(--spacing-01);
|
||||||
|
line-height: 1;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: var(--toolbar-btn-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&:not(:disabled) {
|
||||||
|
background-color: var(--pdf-toolbar-btn-hover-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-content {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.log-btn {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: var(--white);
|
||||||
|
background-color: var(--link-color);
|
||||||
|
box-shadow: none;
|
||||||
|
opacity: 0.65;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&:not(:disabled) {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--toolbar-btn-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf {
|
||||||
|
background-color: var(--pdf-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-viewer,
|
||||||
|
.pdf-logs,
|
||||||
|
.pdf-errors,
|
||||||
|
.pdf-uncompiled {
|
||||||
|
@extend .full-size;
|
||||||
|
|
||||||
|
top: var(--toolbar-small-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-viewer {
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-viewer {
|
||||||
|
@extend .full-size;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
|
.canvasWrapper > canvas,
|
||||||
|
div.pdf-canvas {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 10px rgb(0 0 0 / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.pdf-canvas.pdfng-empty {
|
||||||
|
background-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.pdf-canvas.pdfng-loading {
|
||||||
|
background-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
margin: var(--spacing-05) auto;
|
||||||
|
padding: 0 var(--spacing-05);
|
||||||
|
box-sizing: content-box;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
box-sizing: content-box;
|
||||||
|
margin: var(--spacing-05) auto;
|
||||||
|
box-shadow: 0 0 8px #bbb;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-viewer-inner {
|
||||||
|
position: absolute;
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
-webkit-font-smoothing: initial;
|
||||||
|
-moz-osx-font-smoothing: initial;
|
||||||
|
|
||||||
|
/* fix review-panel overflow issue, see: https://github.com/overleaf/internal/issues/6781#issuecomment-1112708638 */
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
|
.pdfViewer {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
|
.textLayer br::selection {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-thin {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
height: 3px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--link-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-viewer-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-zoom-controls {
|
||||||
|
display: inline-flex;
|
||||||
|
border-left: 1px solid rgb(125 125 125 / 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-toolbar-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-04);
|
||||||
|
margin-left: var(--spacing-04);
|
||||||
|
margin-right: var(--spacing-04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-toolbar-button {
|
||||||
|
padding: var(--spacing-01) !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-zoom-dropdown-button {
|
||||||
|
width: 60px;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-zoom-dropdown-mac-shortcut-char {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-custom-zoom-menu-item {
|
||||||
|
display: block;
|
||||||
|
pointer-events: initial !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: initial !important;
|
||||||
|
color: initial !important;
|
||||||
|
cursor: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-page-number-input {
|
||||||
|
color: var(--toolbar-btn-color);
|
||||||
|
font-size: var(--font-size-02);
|
||||||
|
padding-right: var(--spacing-04);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-02);
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: initial;
|
||||||
|
border: 1px solid var(--neutral-60);
|
||||||
|
width: 32px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-viewer-controls-small {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-toolbar-popover-button {
|
||||||
|
padding: var(--spacing-01) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfjs-toolbar-popover {
|
||||||
|
background-color: var(--editor-toolbar-bg);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
|
||||||
|
.popover-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--toolbar-btn-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover-body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-04) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new viewer UI has overflow on the inner element,
|
||||||
|
// so disable the overflow on the outer element
|
||||||
|
.pdf-viewer .pdfjs-viewer.pdfjs-viewer-outer {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:fullscreen {
|
||||||
|
.pdfjs-viewer-inner {
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.synctex-control {
|
||||||
|
> .synctex-control-icon {
|
||||||
|
display: inline-block;
|
||||||
|
font:
|
||||||
|
normal normal normal 14px/1 FontAwesome,
|
||||||
|
sans-serif;
|
||||||
|
font-size: inherit;
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .synctex-spin-icon {
|
||||||
|
margin-top: var(--spacing-01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-tooltip {
|
||||||
|
.tooltip-inner {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@
|
||||||
--toolbar-btn-hover-color: var(--white);
|
--toolbar-btn-hover-color: var(--white);
|
||||||
--toolbar-btn-active-color: var(--white);
|
--toolbar-btn-active-color: var(--white);
|
||||||
--toolbar-btn-active-bg-color: var(--green-50);
|
--toolbar-btn-active-bg-color: var(--green-50);
|
||||||
|
--toolbar-small-height: 32px;
|
||||||
|
--toolbar-alt-bg-color: var(--neutral-80);
|
||||||
--formatting-btn-color: var(--white);
|
--formatting-btn-color: var(--white);
|
||||||
--formatting-btn-bg: var(--neutral-80);
|
--formatting-btn-bg: var(--neutral-80);
|
||||||
--formatting-btn-border: var(--neutral-70);
|
--formatting-btn-border: var(--neutral-70);
|
||||||
|
@ -27,6 +29,7 @@
|
||||||
--toolbar-btn-hover-bg-color: var(--neutral-10);
|
--toolbar-btn-hover-bg-color: var(--neutral-10);
|
||||||
--toolbar-btn-hover-color: var(--neutral-70);
|
--toolbar-btn-hover-color: var(--neutral-70);
|
||||||
--toolbar-btn-active-bg-color: var(--green-50);
|
--toolbar-btn-active-bg-color: var(--green-50);
|
||||||
|
--toolbar-alt-bg-color: var(--white);
|
||||||
--formatting-btn-color: var(--neutral-70);
|
--formatting-btn-color: var(--neutral-70);
|
||||||
--formatting-btn-bg: transparent;
|
--formatting-btn-bg: transparent;
|
||||||
--formatting-btn-border: var(--neutral-20);
|
--formatting-btn-border: var(--neutral-20);
|
||||||
|
@ -43,7 +46,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: var(--toolbar-height);
|
height: var(--toolbar-height);
|
||||||
min-height: var(--toolbar-height);
|
|
||||||
border-bottom: 1px solid var(--toolbar-border-color);
|
border-bottom: 1px solid var(--toolbar-border-color);
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
|
@ -44,7 +44,7 @@ describe('start free trial button', function () {
|
||||||
source="cypress-test"
|
source="cypress-test"
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
size: 'large',
|
size: 'lg',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue