mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[cm6] Improve performance of the editor toolbar (#13915)
* Memoize figure and math dropdowns * Only build section heading overlay when open * Memoise currentLevel * Remove memo from ToolbarOverflow * Calculate listDepth in the container component * Avoid using document.querySelector GitOrigin-RevId: d5ec8817d35d04e0e2c60c8eecc8678ede69f82a
This commit is contained in:
parent
930cec2189
commit
ff7eec48de
7 changed files with 86 additions and 64 deletions
|
@ -21,6 +21,7 @@ import getMeta from '../../../utils/meta'
|
||||||
import { isVisual } from '../extensions/visual/visual'
|
import { isVisual } from '../extensions/visual/visual'
|
||||||
import SplitTestBadge from '../../../shared/components/split-test-badge'
|
import SplitTestBadge from '../../../shared/components/split-test-badge'
|
||||||
import { language } from '@codemirror/language'
|
import { language } from '@codemirror/language'
|
||||||
|
import { minimumListDepthForSelection } from '../utils/tree-operations/ancestors'
|
||||||
|
|
||||||
export const CodeMirrorToolbar = () => {
|
export const CodeMirrorToolbar = () => {
|
||||||
const view = useCodeMirrorViewContext()
|
const view = useCodeMirrorViewContext()
|
||||||
|
@ -48,6 +49,8 @@ const Toolbar = memo(function Toolbar() {
|
||||||
const languageName = state.facet(language)?.name
|
const languageName = state.facet(language)?.name
|
||||||
const visual = isVisual(view)
|
const visual = isVisual(view)
|
||||||
|
|
||||||
|
const listDepth = minimumListDepthForSelection(state)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
open: overflowOpen,
|
open: overflowOpen,
|
||||||
onToggle: setOverflowOpen,
|
onToggle: setOverflowOpen,
|
||||||
|
@ -108,7 +111,12 @@ const Toolbar = memo(function Toolbar() {
|
||||||
return (
|
return (
|
||||||
<div className="ol-cm-toolbar toolbar-editor" ref={elementRef}>
|
<div className="ol-cm-toolbar toolbar-editor" ref={elementRef}>
|
||||||
{showSourceToolbar && <EditorSwitch />}
|
{showSourceToolbar && <EditorSwitch />}
|
||||||
<ToolbarItems state={state} languageName={languageName} visual={visual} />
|
<ToolbarItems
|
||||||
|
state={state}
|
||||||
|
languageName={languageName}
|
||||||
|
visual={visual}
|
||||||
|
listDepth={listDepth}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="ol-cm-toolbar-button-group ol-cm-toolbar-stretch"
|
className="ol-cm-toolbar-button-group ol-cm-toolbar-stretch"
|
||||||
ref={overflowBeforeRef}
|
ref={overflowBeforeRef}
|
||||||
|
@ -125,6 +133,7 @@ const Toolbar = memo(function Toolbar() {
|
||||||
overflowed={overflowedItemsRef.current}
|
overflowed={overflowedItemsRef.current}
|
||||||
languageName={languageName}
|
languageName={languageName}
|
||||||
visual={visual}
|
visual={visual}
|
||||||
|
listDepth={listDepth}
|
||||||
/>
|
/>
|
||||||
</ToolbarOverflow>
|
</ToolbarOverflow>
|
||||||
<div className="formatting-buttons-wrapper" />
|
<div className="formatting-buttons-wrapper" />
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const ToolbarButtonMenu: FC<{
|
||||||
show={open}
|
show={open}
|
||||||
target={target.current}
|
target={target.current}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
container={document.querySelector('.cm-editor')}
|
container={view.dom}
|
||||||
containerPadding={0}
|
containerPadding={0}
|
||||||
animation
|
animation
|
||||||
onHide={() => onToggle(false)}
|
onHide={() => onToggle(false)}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { ListGroupItem } from 'react-bootstrap'
|
import { ListGroupItem } from 'react-bootstrap'
|
||||||
import { ToolbarButtonMenu } from './button-menu'
|
import { ToolbarButtonMenu } from './button-menu'
|
||||||
import Icon from '../../../../shared/components/icon'
|
import Icon from '../../../../shared/components/icon'
|
||||||
import { useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { FigureModalSource } from '../figure-modal/figure-modal-context'
|
import { FigureModalSource } from '../figure-modal/figure-modal-context'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
|
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
|
||||||
import { useCodeMirrorViewContext } from '../codemirror-editor'
|
import { useCodeMirrorViewContext } from '../codemirror-editor'
|
||||||
import { insertFigure } from '../../extensions/toolbar/commands'
|
import { insertFigure } from '../../extensions/toolbar/commands'
|
||||||
|
|
||||||
export const InsertFigureDropdown = () => {
|
export const InsertFigureDropdown = memo(function InsertFigureDropdown() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const view = useCodeMirrorViewContext()
|
const view = useCodeMirrorViewContext()
|
||||||
const openFigureModal = useCallback(
|
const openFigureModal = useCallback(
|
||||||
|
@ -57,4 +57,4 @@ export const InsertFigureDropdown = () => {
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
</ToolbarButtonMenu>
|
</ToolbarButtonMenu>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
|
@ -8,8 +8,9 @@ import {
|
||||||
wrapInDisplayMath,
|
wrapInDisplayMath,
|
||||||
wrapInInlineMath,
|
wrapInInlineMath,
|
||||||
} from '../../extensions/toolbar/commands'
|
} from '../../extensions/toolbar/commands'
|
||||||
|
import { memo } from 'react'
|
||||||
|
|
||||||
export function MathDropdown() {
|
export const MathDropdown = memo(function MathDropdown() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const view = useCodeMirrorViewContext()
|
const view = useCodeMirrorViewContext()
|
||||||
|
|
||||||
|
@ -46,4 +47,4 @@ export function MathDropdown() {
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
</ToolbarButtonMenu>
|
</ToolbarButtonMenu>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { FC, LegacyRef, memo } from 'react'
|
import { FC, LegacyRef } from 'react'
|
||||||
import { Button, Overlay, Popover } from 'react-bootstrap'
|
import { Button, Overlay, Popover } from 'react-bootstrap'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import Icon from '../../../../shared/components/icon'
|
import Icon from '../../../../shared/components/icon'
|
||||||
|
import { useCodeMirrorViewContext } from '../codemirror-editor'
|
||||||
|
|
||||||
export const ToolbarOverflow: FC<{
|
export const ToolbarOverflow: FC<{
|
||||||
overflowed: boolean
|
overflowed: boolean
|
||||||
|
@ -9,14 +10,16 @@ export const ToolbarOverflow: FC<{
|
||||||
overflowOpen: boolean
|
overflowOpen: boolean
|
||||||
setOverflowOpen: (open: boolean) => void
|
setOverflowOpen: (open: boolean) => void
|
||||||
overflowRef?: LegacyRef<Popover>
|
overflowRef?: LegacyRef<Popover>
|
||||||
}> = memo(function ToolbarOverflow({
|
}> = ({
|
||||||
overflowed,
|
overflowed,
|
||||||
target,
|
target,
|
||||||
overflowOpen,
|
overflowOpen,
|
||||||
setOverflowOpen,
|
setOverflowOpen,
|
||||||
overflowRef,
|
overflowRef,
|
||||||
children,
|
children,
|
||||||
}) {
|
}) => {
|
||||||
|
const view = useCodeMirrorViewContext()
|
||||||
|
|
||||||
const className = classnames(
|
const className = classnames(
|
||||||
'ol-cm-toolbar-button',
|
'ol-cm-toolbar-button',
|
||||||
'ol-cm-toolbar-overflow-toggle',
|
'ol-cm-toolbar-overflow-toggle',
|
||||||
|
@ -48,7 +51,7 @@ export const ToolbarOverflow: FC<{
|
||||||
show={overflowOpen}
|
show={overflowOpen}
|
||||||
target={target}
|
target={target}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
container={document.querySelector('.cm-editor')}
|
container={view.dom}
|
||||||
containerPadding={0}
|
containerPadding={0}
|
||||||
animation
|
animation
|
||||||
onHide={() => setOverflowOpen(false)}
|
onHide={() => setOverflowOpen(false)}
|
||||||
|
@ -59,4 +62,4 @@ export const ToolbarOverflow: FC<{
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
findCurrentSectionHeadingLevel,
|
findCurrentSectionHeadingLevel,
|
||||||
setSectionHeadingLevel,
|
setSectionHeadingLevel,
|
||||||
} from '../../extensions/toolbar/sections'
|
} from '../../extensions/toolbar/sections'
|
||||||
import { useCallback, useRef } from 'react'
|
import { useCallback, useMemo, useRef } from 'react'
|
||||||
import { Overlay, Popover } from 'react-bootstrap'
|
import { Overlay, Popover } from 'react-bootstrap'
|
||||||
import useEventListener from '../../../../shared/hooks/use-event-listener'
|
import useEventListener from '../../../../shared/hooks/use-event-listener'
|
||||||
import useDropdown from '../../../../shared/hooks/use-dropdown'
|
import useDropdown from '../../../../shared/hooks/use-dropdown'
|
||||||
|
@ -42,7 +42,11 @@ export const SectionHeadingDropdown = () => {
|
||||||
|
|
||||||
const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
|
const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
const currentLevel = findCurrentSectionHeadingLevel(state)
|
const currentLevel = useMemo(
|
||||||
|
() => findCurrentSectionHeadingLevel(state),
|
||||||
|
[state]
|
||||||
|
)
|
||||||
|
|
||||||
const currentLabel = currentLevel
|
const currentLabel = currentLevel
|
||||||
? levels.get(currentLevel.level) ?? currentLevel.level
|
? levels.get(currentLevel.level) ?? currentLevel.level
|
||||||
: '---'
|
: '---'
|
||||||
|
@ -64,52 +68,54 @@ export const SectionHeadingDropdown = () => {
|
||||||
<Icon type="caret-down" fw />
|
<Icon type="caret-down" fw />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Overlay
|
{overflowOpen && (
|
||||||
show={overflowOpen}
|
<Overlay
|
||||||
onHide={() => setOverflowOpen(false)}
|
show
|
||||||
animation={false}
|
onHide={() => setOverflowOpen(false)}
|
||||||
container={document.querySelector('.cm-editor')}
|
animation={false}
|
||||||
containerPadding={0}
|
container={view.dom}
|
||||||
placement="bottom"
|
containerPadding={0}
|
||||||
rootClose
|
placement="bottom"
|
||||||
target={toggleButtonRef.current ?? undefined}
|
rootClose
|
||||||
>
|
target={toggleButtonRef.current ?? undefined}
|
||||||
<Popover
|
|
||||||
id="popover-toolbar-section-heading"
|
|
||||||
className="ol-cm-toolbar-menu-popover"
|
|
||||||
>
|
>
|
||||||
<div
|
<Popover
|
||||||
className="ol-cm-toolbar-menu"
|
id="popover-toolbar-section-heading"
|
||||||
id="section-heading-menu"
|
className="ol-cm-toolbar-menu-popover"
|
||||||
role="menu"
|
|
||||||
aria-labelledby="section-heading-menu-button"
|
|
||||||
>
|
>
|
||||||
{levelsEntries.map(([level, label]) => (
|
<div
|
||||||
<button
|
className="ol-cm-toolbar-menu"
|
||||||
type="button"
|
id="section-heading-menu"
|
||||||
role="menuitem"
|
role="menu"
|
||||||
key={level}
|
aria-labelledby="section-heading-menu-button"
|
||||||
onClick={() => {
|
>
|
||||||
emitToolbarEvent(view, 'section-level-change')
|
{levelsEntries.map(([level, label]) => (
|
||||||
setSectionHeadingLevel(view, level)
|
<button
|
||||||
view.focus()
|
type="button"
|
||||||
setOverflowOpen(false)
|
role="menuitem"
|
||||||
}}
|
key={level}
|
||||||
className={classnames(
|
onClick={() => {
|
||||||
'ol-cm-toolbar-menu-item',
|
emitToolbarEvent(view, 'section-level-change')
|
||||||
`section-level-${level}`,
|
setSectionHeadingLevel(view, level)
|
||||||
{
|
view.focus()
|
||||||
'ol-cm-toolbar-menu-item-active':
|
setOverflowOpen(false)
|
||||||
level === currentLevel?.level,
|
}}
|
||||||
}
|
className={classnames(
|
||||||
)}
|
'ol-cm-toolbar-menu-item',
|
||||||
>
|
`section-level-${level}`,
|
||||||
{label}
|
{
|
||||||
</button>
|
'ol-cm-toolbar-menu-item-active':
|
||||||
))}
|
level === currentLevel?.level,
|
||||||
</div>
|
}
|
||||||
</Popover>
|
)}
|
||||||
</Overlay>
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</Overlay>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@ import { EditorView } from '@codemirror/view'
|
||||||
import { useEditorContext } from '../../../../shared/context/editor-context'
|
import { useEditorContext } from '../../../../shared/context/editor-context'
|
||||||
import useScopeEventEmitter from '../../../../shared/hooks/use-scope-event-emitter'
|
import useScopeEventEmitter from '../../../../shared/hooks/use-scope-event-emitter'
|
||||||
import { useLayoutContext } from '../../../../shared/context/layout-context'
|
import { useLayoutContext } from '../../../../shared/context/layout-context'
|
||||||
import {
|
import { withinFormattingCommand } from '../../utils/tree-operations/ancestors'
|
||||||
minimumListDepthForSelection,
|
|
||||||
withinFormattingCommand,
|
|
||||||
} from '../../utils/tree-operations/ancestors'
|
|
||||||
import { ToolbarButton } from './toolbar-button'
|
import { ToolbarButton } from './toolbar-button'
|
||||||
import { redo, undo } from '@codemirror/commands'
|
import { redo, undo } from '@codemirror/commands'
|
||||||
import * as commands from '../../extensions/toolbar/commands'
|
import * as commands from '../../extensions/toolbar/commands'
|
||||||
|
@ -25,11 +22,17 @@ export const ToolbarItems: FC<{
|
||||||
overflowed?: Set<string>
|
overflowed?: Set<string>
|
||||||
languageName?: string
|
languageName?: string
|
||||||
visual: boolean
|
visual: boolean
|
||||||
}> = memo(function ToolbarItems({ state, overflowed, languageName, visual }) {
|
listDepth: number
|
||||||
|
}> = memo(function ToolbarItems({
|
||||||
|
state,
|
||||||
|
overflowed,
|
||||||
|
languageName,
|
||||||
|
visual,
|
||||||
|
listDepth,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { toggleSymbolPalette, showSymbolPalette } = useEditorContext()
|
const { toggleSymbolPalette, showSymbolPalette } = useEditorContext()
|
||||||
const isActive = withinFormattingCommand(state)
|
const isActive = withinFormattingCommand(state)
|
||||||
const listDepth = minimumListDepthForSelection(state)
|
|
||||||
const addCommentEmitter = useScopeEventEmitter('comment:start_adding')
|
const addCommentEmitter = useScopeEventEmitter('comment:start_adding')
|
||||||
const { setReviewPanelOpen } = useLayoutContext()
|
const { setReviewPanelOpen } = useLayoutContext()
|
||||||
const splitTestVariants = getMeta('ol-splitTestVariants', {})
|
const splitTestVariants = getMeta('ol-splitTestVariants', {})
|
||||||
|
|
Loading…
Reference in a new issue