|
|
@@ -1,5 +1,16 @@
|
|
|
-import { memo, ReactNode, useCallback } from 'react'
|
|
|
-import { Dropdown, MenuItem, MenuItemProps } from 'react-bootstrap'
|
|
|
+import { memo, ReactNode, useCallback, forwardRef } from 'react'
|
|
|
+import {
|
|
|
+ Dropdown as BS3Dropdown,
|
|
|
+ MenuItem,
|
|
|
+ MenuItemProps,
|
|
|
+} from 'react-bootstrap'
|
|
|
+import { Spinner } from 'react-bootstrap-5'
|
|
|
+import {
|
|
|
+ Dropdown,
|
|
|
+ DropdownItem,
|
|
|
+ DropdownMenu,
|
|
|
+ DropdownToggle,
|
|
|
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
|
|
import { Trans, useTranslation } from 'react-i18next'
|
|
|
import Tooltip from '../../../shared/components/tooltip'
|
|
|
import Icon from '../../../shared/components/icon'
|
|
|
@@ -13,6 +24,8 @@ import {
|
|
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
|
|
import useEventListener from '../../../shared/hooks/use-event-listener'
|
|
|
import { DetachRole } from '@/shared/context/detach-context'
|
|
|
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
|
|
+import MaterialIcon from '@/shared/components/material-icon'
|
|
|
|
|
|
function IconPlaceholder() {
|
|
|
return <Icon type="" fw />
|
|
|
@@ -42,7 +55,7 @@ function IconPdfOnly() {
|
|
|
return <Icon type="file-pdf-o" fw />
|
|
|
}
|
|
|
|
|
|
-function IconCheckmark({
|
|
|
+const isActiveDropdownItem = ({
|
|
|
iconFor,
|
|
|
pdfLayout,
|
|
|
view,
|
|
|
@@ -52,23 +65,26 @@ function IconCheckmark({
|
|
|
pdfLayout: IdeLayout
|
|
|
view: IdeView | null
|
|
|
detachRole?: DetachRole
|
|
|
-}) {
|
|
|
+}) => {
|
|
|
if (detachRole === 'detacher' || view === 'history') {
|
|
|
- return <IconPlaceholder />
|
|
|
+ return false
|
|
|
}
|
|
|
if (
|
|
|
iconFor === 'editorOnly' &&
|
|
|
pdfLayout === 'flat' &&
|
|
|
(view === 'editor' || view === 'file')
|
|
|
) {
|
|
|
- return <IconChecked />
|
|
|
+ return true
|
|
|
} else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') {
|
|
|
- return <IconChecked />
|
|
|
+ return true
|
|
|
} else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') {
|
|
|
- return <IconChecked />
|
|
|
+ return true
|
|
|
}
|
|
|
- // return empty icon for placeholder
|
|
|
- return <IconPlaceholder />
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+function IconCheckmark(props: Parameters<typeof isActiveDropdownItem>[0]) {
|
|
|
+ return isActiveDropdownItem(props) ? <IconChecked /> : <IconPlaceholder />
|
|
|
}
|
|
|
|
|
|
function LayoutMenuItem({
|
|
|
@@ -94,6 +110,35 @@ function LayoutMenuItem({
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+function EnhancedDropdownItem({
|
|
|
+ active,
|
|
|
+ ...props
|
|
|
+}: React.ComponentProps<typeof DropdownItem>) {
|
|
|
+ return (
|
|
|
+ <DropdownItem
|
|
|
+ active={active}
|
|
|
+ aria-current={active}
|
|
|
+ trailingIcon={active ? 'check' : null}
|
|
|
+ {...props}
|
|
|
+ />
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const LayoutDropdownToggleButton = forwardRef<
|
|
|
+ HTMLButtonElement,
|
|
|
+ {
|
|
|
+ onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
|
|
+ }
|
|
|
+>(({ onClick, ...props }, ref) => {
|
|
|
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
|
+ eventTracking.sendMB('navigation-clicked-layout')
|
|
|
+ onClick(e)
|
|
|
+ }
|
|
|
+
|
|
|
+ return <button {...props} ref={ref} onClick={handleClick} />
|
|
|
+})
|
|
|
+LayoutDropdownToggleButton.displayName = 'LayoutDropdownToggleButton'
|
|
|
+
|
|
|
function DetachDisabled() {
|
|
|
const { t } = useTranslation()
|
|
|
|
|
|
@@ -164,97 +209,209 @@ function LayoutDropdownButton() {
|
|
|
{t('layout_processing')}
|
|
|
</div>
|
|
|
)}
|
|
|
- <ControlledDropdown
|
|
|
- id="layout-dropdown"
|
|
|
- onMainButtonClick={() => {
|
|
|
- eventTracking.sendMB('navigation-clicked-layout')
|
|
|
- }}
|
|
|
- className="toolbar-item layout-dropdown"
|
|
|
- pullRight
|
|
|
- >
|
|
|
- <Dropdown.Toggle className="btn-full-height" bsStyle="link">
|
|
|
- {processing ? <IconRefresh /> : <IconLayout />}
|
|
|
- <span className="toolbar-label">{t('layout')}</span>
|
|
|
- </Dropdown.Toggle>
|
|
|
- <Dropdown.Menu className="layout-dropdown-list">
|
|
|
- <LayoutMenuItem
|
|
|
- onSelect={() => handleChangeLayout('sideBySide')}
|
|
|
- checkmark={
|
|
|
- <IconCheckmark
|
|
|
- iconFor="sideBySide"
|
|
|
- pdfLayout={pdfLayout}
|
|
|
- view={view}
|
|
|
- detachRole={detachRole}
|
|
|
+ <BootstrapVersionSwitcher
|
|
|
+ bs3={
|
|
|
+ <ControlledDropdown
|
|
|
+ id="layout-dropdown"
|
|
|
+ onMainButtonClick={() => {
|
|
|
+ eventTracking.sendMB('navigation-clicked-layout')
|
|
|
+ }}
|
|
|
+ className="toolbar-item layout-dropdown"
|
|
|
+ pullRight
|
|
|
+ >
|
|
|
+ <BS3Dropdown.Toggle className="btn-full-height" bsStyle="link">
|
|
|
+ {processing ? <IconRefresh /> : <IconLayout />}
|
|
|
+ <span className="toolbar-label">{t('layout')}</span>
|
|
|
+ </BS3Dropdown.Toggle>
|
|
|
+ <BS3Dropdown.Menu className="layout-dropdown-list">
|
|
|
+ <LayoutMenuItem
|
|
|
+ onSelect={() => handleChangeLayout('sideBySide')}
|
|
|
+ checkmark={
|
|
|
+ <IconCheckmark
|
|
|
+ iconFor="sideBySide"
|
|
|
+ pdfLayout={pdfLayout}
|
|
|
+ view={view}
|
|
|
+ detachRole={detachRole}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ icon={<IconSplit />}
|
|
|
+ text={t('editor_and_pdf')}
|
|
|
/>
|
|
|
- }
|
|
|
- icon={<IconSplit />}
|
|
|
- text={t('editor_and_pdf')}
|
|
|
- />
|
|
|
|
|
|
- <LayoutMenuItem
|
|
|
- onSelect={() => handleChangeLayout('flat', 'editor')}
|
|
|
- checkmark={
|
|
|
- <IconCheckmark
|
|
|
- iconFor="editorOnly"
|
|
|
- pdfLayout={pdfLayout}
|
|
|
- view={view}
|
|
|
- detachRole={detachRole}
|
|
|
+ <LayoutMenuItem
|
|
|
+ onSelect={() => handleChangeLayout('flat', 'editor')}
|
|
|
+ checkmark={
|
|
|
+ <IconCheckmark
|
|
|
+ iconFor="editorOnly"
|
|
|
+ pdfLayout={pdfLayout}
|
|
|
+ view={view}
|
|
|
+ detachRole={detachRole}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ icon={<IconEditorOnly />}
|
|
|
+ text={
|
|
|
+ <Trans
|
|
|
+ i18nKey="editor_only_hide_pdf"
|
|
|
+ components={[
|
|
|
+ <span key="editor_only_hide_pdf" className="subdued" />,
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ }
|
|
|
/>
|
|
|
- }
|
|
|
- icon={<IconEditorOnly />}
|
|
|
- text={
|
|
|
- <Trans
|
|
|
- i18nKey="editor_only_hide_pdf"
|
|
|
- components={[
|
|
|
- <span key="editor_only_hide_pdf" className="subdued" />,
|
|
|
- ]}
|
|
|
- />
|
|
|
- }
|
|
|
- />
|
|
|
|
|
|
- <LayoutMenuItem
|
|
|
- onSelect={() => handleChangeLayout('flat', 'pdf')}
|
|
|
- checkmark={
|
|
|
- <IconCheckmark
|
|
|
- iconFor="pdfOnly"
|
|
|
- pdfLayout={pdfLayout}
|
|
|
- view={view}
|
|
|
- detachRole={detachRole}
|
|
|
- />
|
|
|
- }
|
|
|
- icon={<IconPdfOnly />}
|
|
|
- text={
|
|
|
- <Trans
|
|
|
- i18nKey="pdf_only_hide_editor"
|
|
|
- components={[
|
|
|
- <span key="pdf_only_hide_editor" className="subdued" />,
|
|
|
- ]}
|
|
|
+ <LayoutMenuItem
|
|
|
+ onSelect={() => handleChangeLayout('flat', 'pdf')}
|
|
|
+ checkmark={
|
|
|
+ <IconCheckmark
|
|
|
+ iconFor="pdfOnly"
|
|
|
+ pdfLayout={pdfLayout}
|
|
|
+ view={view}
|
|
|
+ detachRole={detachRole}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ icon={<IconPdfOnly />}
|
|
|
+ text={
|
|
|
+ <Trans
|
|
|
+ i18nKey="pdf_only_hide_editor"
|
|
|
+ components={[
|
|
|
+ <span key="pdf_only_hide_editor" className="subdued" />,
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ }
|
|
|
/>
|
|
|
- }
|
|
|
- />
|
|
|
|
|
|
- {'BroadcastChannel' in window ? (
|
|
|
- <LayoutMenuItem
|
|
|
- onSelect={() => handleDetach()}
|
|
|
- checkmark={
|
|
|
- detachRole === 'detacher' ? (
|
|
|
- detachIsLinked ? (
|
|
|
- <IconChecked />
|
|
|
- ) : (
|
|
|
- <IconRefresh />
|
|
|
- )
|
|
|
- ) : (
|
|
|
- <IconPlaceholder />
|
|
|
- )
|
|
|
- }
|
|
|
- icon={<IconDetach />}
|
|
|
- text={t('pdf_in_separate_tab')}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <DetachDisabled />
|
|
|
- )}
|
|
|
- </Dropdown.Menu>
|
|
|
- </ControlledDropdown>
|
|
|
+ {'BroadcastChannel' in window ? (
|
|
|
+ <LayoutMenuItem
|
|
|
+ onSelect={() => handleDetach()}
|
|
|
+ checkmark={
|
|
|
+ detachRole === 'detacher' ? (
|
|
|
+ detachIsLinked ? (
|
|
|
+ <IconChecked />
|
|
|
+ ) : (
|
|
|
+ <IconRefresh />
|
|
|
+ )
|
|
|
+ ) : (
|
|
|
+ <IconPlaceholder />
|
|
|
+ )
|
|
|
+ }
|
|
|
+ icon={<IconDetach />}
|
|
|
+ text={t('pdf_in_separate_tab')}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <DetachDisabled />
|
|
|
+ )}
|
|
|
+ </BS3Dropdown.Menu>
|
|
|
+ </ControlledDropdown>
|
|
|
+ }
|
|
|
+ bs5={
|
|
|
+ <Dropdown className="toolbar-item layout-dropdown" align="end">
|
|
|
+ <DropdownToggle
|
|
|
+ id="layout-dropdown-btn"
|
|
|
+ className="btn-full-height"
|
|
|
+ as={LayoutDropdownToggleButton}
|
|
|
+ >
|
|
|
+ {processing ? (
|
|
|
+ <Spinner
|
|
|
+ animation="border"
|
|
|
+ aria-hidden="true"
|
|
|
+ size="sm"
|
|
|
+ role="status"
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <MaterialIcon type="dock_to_right" className="align-middle" />
|
|
|
+ )}
|
|
|
+ <span className="toolbar-label">{t('layout')}</span>
|
|
|
+ </DropdownToggle>
|
|
|
+ <DropdownMenu className="layout-dropdown-list">
|
|
|
+ <EnhancedDropdownItem
|
|
|
+ onClick={() => handleChangeLayout('sideBySide')}
|
|
|
+ active={isActiveDropdownItem({
|
|
|
+ iconFor: 'sideBySide',
|
|
|
+ pdfLayout,
|
|
|
+ view,
|
|
|
+ detachRole,
|
|
|
+ })}
|
|
|
+ leadingIcon="dock_to_right"
|
|
|
+ >
|
|
|
+ {t('editor_and_pdf')}
|
|
|
+ </EnhancedDropdownItem>
|
|
|
+
|
|
|
+ <EnhancedDropdownItem
|
|
|
+ onClick={() => handleChangeLayout('flat', 'editor')}
|
|
|
+ active={isActiveDropdownItem({
|
|
|
+ iconFor: 'editorOnly',
|
|
|
+ pdfLayout,
|
|
|
+ view,
|
|
|
+ detachRole,
|
|
|
+ })}
|
|
|
+ leadingIcon="code"
|
|
|
+ >
|
|
|
+ <Trans
|
|
|
+ i18nKey="editor_only_hide_pdf"
|
|
|
+ components={[
|
|
|
+ <span
|
|
|
+ key="editor_only_hide_pdf"
|
|
|
+ className="ms-1 subdued"
|
|
|
+ />,
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ </EnhancedDropdownItem>
|
|
|
+
|
|
|
+ <EnhancedDropdownItem
|
|
|
+ onClick={() => handleChangeLayout('flat', 'pdf')}
|
|
|
+ active={isActiveDropdownItem({
|
|
|
+ iconFor: 'pdfOnly',
|
|
|
+ pdfLayout,
|
|
|
+ view,
|
|
|
+ detachRole,
|
|
|
+ })}
|
|
|
+ leadingIcon="picture_as_pdf"
|
|
|
+ >
|
|
|
+ <Trans
|
|
|
+ i18nKey="pdf_only_hide_editor"
|
|
|
+ components={[
|
|
|
+ <span
|
|
|
+ key="pdf_only_hide_editor"
|
|
|
+ className="ms-1 subdued"
|
|
|
+ />,
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ </EnhancedDropdownItem>
|
|
|
+
|
|
|
+ {'BroadcastChannel' in window ? (
|
|
|
+ <EnhancedDropdownItem
|
|
|
+ onClick={() => handleDetach()}
|
|
|
+ active={detachRole === 'detacher' && detachIsLinked}
|
|
|
+ trailingIcon={
|
|
|
+ detachRole === 'detacher' ? (
|
|
|
+ detachIsLinked ? (
|
|
|
+ 'check'
|
|
|
+ ) : (
|
|
|
+ <span className="spinner-container">
|
|
|
+ <Spinner
|
|
|
+ animation="border"
|
|
|
+ aria-hidden="true"
|
|
|
+ size="sm"
|
|
|
+ role="status"
|
|
|
+ />
|
|
|
+ <span className="visually-hidden">
|
|
|
+ {t('loading')}
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ ) : null
|
|
|
+ }
|
|
|
+ leadingIcon="select_window"
|
|
|
+ >
|
|
|
+ {t('pdf_in_separate_tab')}
|
|
|
+ </EnhancedDropdownItem>
|
|
|
+ ) : (
|
|
|
+ <DetachDisabled />
|
|
|
+ )}
|
|
|
+ </DropdownMenu>
|
|
|
+ </Dropdown>
|
|
|
+ }
|
|
|
+ />
|
|
|
</>
|
|
|
)
|
|
|
}
|