Memoize some editor React components (#28963)

GitOrigin-RevId: 6440df9ac3ef1bf1839dff07eb2f55b52b581d3e
This commit is contained in:
Alf Eaton
2025-10-14 10:12:33 +01:00
committed by Copybot
parent f6229d6fe1
commit 5b2e541186
6 changed files with 158 additions and 154 deletions

View File

@@ -1,7 +1,7 @@
import MaterialIcon, { IconProps } from '@/shared/components/material-icon'
export const LinkedFileIcon = (
props: Omit<IconProps, 'type' | 'modifier' | 'className' | 'unfilled'>
props: Omit<IconProps, 'type' | 'modifier' | 'className' | 'unfilled' | 'ref'>
) => {
return (
<MaterialIcon

View File

@@ -1,3 +1,4 @@
import { memo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import HotkeysModalBottomText from './hotkeys-modal-bottom-text'
import {
@@ -11,7 +12,7 @@ import OLButton from '@/shared/components/ol/ol-button'
import OLRow from '@/shared/components/ol/ol-row'
import OLCol from '@/shared/components/ol/ol-col'
export default function HotkeysModal({
export default memo(function HotkeysModal({
animation = true,
handleHide,
show,
@@ -205,7 +206,7 @@ export default function HotkeysModal({
</OLModalFooter>
</OLModal>
)
}
})
function Hotkey({
combination,

View File

@@ -7,20 +7,20 @@ import { OLToast } from '@/shared/components/ol/ol-toast'
import { OLToastContainer } from '@/shared/components/ol/ol-toast-container'
import { useEditorContext } from '@/shared/context/editor-context'
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { sendMB } from '@/infrastructure/event-tracking'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import { useTranslation } from 'react-i18next'
type EditorSurveyPage = 'ease-of-use' | 'meets-my-needs' | 'thank-you'
export default function EditorSurvey() {
export default memo(function EditorSurvey() {
return (
<OLToastContainer className="editor-survey-toast">
<EditorSurveyContent />
</OLToastContainer>
)
}
})
const TUTORIAL_KEY = 'editor-popup-ux-survey'

View File

@@ -1,6 +1,6 @@
import { OLToast, OLToastProps } from '@/shared/components/ol/ol-toast'
import useEventListener from '@/shared/hooks/use-event-listener'
import { Fragment, ReactElement, useCallback, useState } from 'react'
import { Fragment, memo, ReactElement, useCallback, useState } from 'react'
import { debugConsole } from '@/utils/debugging'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
@@ -30,7 +30,7 @@ const GENERATOR_MAP: Map<string, GlobalToastGenerator> = new Map(
let toastCounter = 1
export const GlobalToasts = () => {
export const GlobalToasts = memo(function GlobalToasts() {
const [toasts, setToasts] = useState<
{ component: ReactElement; id: string }[]
>([])
@@ -96,4 +96,4 @@ export const GlobalToasts = () => {
))}
</OLToastContainer>
)
}
})

View File

@@ -1,6 +1,7 @@
import {
CSSProperties,
FC,
memo,
useCallback,
useEffect,
useMemo,
@@ -94,166 +95,168 @@ const ReviewTooltipMenu: FC = () => {
)
}
const ReviewTooltipMenuContent: FC<{ onAddComment: () => void }> = ({
onAddComment,
}) => {
const { t } = useTranslation()
const view = useCodeMirrorViewContext()
const state = useCodeMirrorStateContext()
const { reviewPanelOpen } = useLayoutContext()
const ranges = useRangesContext()
const { acceptChanges, rejectChanges } = useRangesActionsContext()
const { showGenericConfirmModal } = useModalsContext()
const { wantTrackChanges } = useEditorPropertiesContext()
const [tooltipStyle, setTooltipStyle] = useState<CSSProperties | undefined>()
const [visible, setVisible] = useState(false)
const ReviewTooltipMenuContent = memo<{ onAddComment: () => void }>(
function ReviewTooltipMenuContent({ onAddComment }) {
const { t } = useTranslation()
const view = useCodeMirrorViewContext()
const state = useCodeMirrorStateContext()
const { reviewPanelOpen } = useLayoutContext()
const ranges = useRangesContext()
const { acceptChanges, rejectChanges } = useRangesActionsContext()
const { showGenericConfirmModal } = useModalsContext()
const { wantTrackChanges } = useEditorPropertiesContext()
const [tooltipStyle, setTooltipStyle] = useState<
CSSProperties | undefined
>()
const [visible, setVisible] = useState(false)
const changesInSelection = useMemo(() => {
return (ranges?.changes ?? []).filter(({ op }) => {
const opFrom = op.p
const opLength = isInsertOperation(op) ? op.i.length : 0
const opTo = opFrom + opLength
const selection = state.selection.main
return opFrom >= selection.from && opTo <= selection.to
})
}, [ranges, state.selection.main])
const changesInSelection = useMemo(() => {
return (ranges?.changes ?? []).filter(({ op }) => {
const opFrom = op.p
const opLength = isInsertOperation(op) ? op.i.length : 0
const opTo = opFrom + opLength
const selection = state.selection.main
return opFrom >= selection.from && opTo <= selection.to
})
}, [ranges, state.selection.main])
const acceptChangesHandler = useCallback(() => {
const nChanges = numberOfChangesInSelection(
const acceptChangesHandler = useCallback(() => {
const nChanges = numberOfChangesInSelection(
ranges,
view.state.selection.main
)
showGenericConfirmModal({
message: t('confirm_accept_selected_changes', { count: nChanges }),
title: t('accept_selected_changes'),
onConfirm: async () => {
await acceptChanges(...changesInSelection)
},
primaryVariant: 'danger',
})
}, [
acceptChanges,
changesInSelection,
ranges,
view.state.selection.main
)
showGenericConfirmModal({
message: t('confirm_accept_selected_changes', { count: nChanges }),
title: t('accept_selected_changes'),
onConfirm: async () => {
await acceptChanges(...changesInSelection)
},
primaryVariant: 'danger',
})
}, [
acceptChanges,
changesInSelection,
ranges,
showGenericConfirmModal,
view,
t,
])
showGenericConfirmModal,
view,
t,
])
const rejectChangesHandler = useCallback(() => {
const nChanges = numberOfChangesInSelection(
const rejectChangesHandler = useCallback(() => {
const nChanges = numberOfChangesInSelection(
ranges,
view.state.selection.main
)
showGenericConfirmModal({
message: t('confirm_reject_selected_changes', { count: nChanges }),
title: t('reject_selected_changes'),
onConfirm: async () => {
await rejectChanges(...changesInSelection)
},
primaryVariant: 'danger',
})
}, [
showGenericConfirmModal,
t,
ranges,
view.state.selection.main
)
showGenericConfirmModal({
message: t('confirm_reject_selected_changes', { count: nChanges }),
title: t('reject_selected_changes'),
onConfirm: async () => {
await rejectChanges(...changesInSelection)
},
primaryVariant: 'danger',
})
}, [
showGenericConfirmModal,
t,
ranges,
view,
rejectChanges,
changesInSelection,
])
view,
rejectChanges,
changesInSelection,
])
const showChangesButtons = changesInSelection.length > 0
const showChangesButtons = changesInSelection.length > 0
useEffect(() => {
view.requestMeasure({
key: 'review-tooltip-outside-viewport',
read(view) {
const cursorCoords = view.coordsAtPos(view.state.selection.main.head)
useEffect(() => {
view.requestMeasure({
key: 'review-tooltip-outside-viewport',
read(view) {
const cursorCoords = view.coordsAtPos(view.state.selection.main.head)
if (!cursorCoords) {
return
}
if (!cursorCoords) {
return
}
const scrollDomRect = view.scrollDOM.getBoundingClientRect()
const contentDomRect = view.contentDOM.getBoundingClientRect()
const editorRightPos = contentDomRect.right - CM_LINE_RIGHT_PADDING
const scrollDomRect = view.scrollDOM.getBoundingClientRect()
const contentDomRect = view.contentDOM.getBoundingClientRect()
const editorRightPos = contentDomRect.right - CM_LINE_RIGHT_PADDING
if (
cursorCoords.top > scrollDomRect.top &&
cursorCoords.top < scrollDomRect.bottom
) {
return
}
if (
cursorCoords.top > scrollDomRect.top &&
cursorCoords.top < scrollDomRect.bottom
) {
return
}
return {
position: 'fixed' as const,
top: scrollDomRect.top + EDIT_MODE_SWITCH_WIDGET_HEIGHT,
right: window.innerWidth - editorRightPos,
}
},
write(res) {
setTooltipStyle(res)
},
})
}, [view, reviewPanelOpen, wantTrackChanges])
return {
position: 'fixed' as const,
top: scrollDomRect.top + EDIT_MODE_SWITCH_WIDGET_HEIGHT,
right: window.innerWidth - editorRightPos,
}
},
write(res) {
setTooltipStyle(res)
},
})
}, [view, reviewPanelOpen, wantTrackChanges])
useEffect(() => {
setVisible(false)
const timeout = setTimeout(() => {
setVisible(true)
}, TOOLTIP_SHOW_DELAY)
useEffect(() => {
setVisible(false)
const timeout = setTimeout(() => {
setVisible(true)
}, TOOLTIP_SHOW_DELAY)
return () => {
clearTimeout(timeout)
}
}, [])
return () => {
clearTimeout(timeout)
}
}, [])
return (
<div
className={classNames('review-tooltip-menu', {
'review-tooltip-menu-visible': visible,
})}
style={tooltipStyle}
>
<button
className="review-tooltip-menu-button review-tooltip-add-comment-button"
onClick={onAddComment}
return (
<div
className={classNames('review-tooltip-menu', {
'review-tooltip-menu-visible': visible,
})}
style={tooltipStyle}
>
<MaterialIcon type="chat" />
{t('add_comment')}
</button>
{showChangesButtons && (
<>
<div className="review-tooltip-menu-divider" />
<OLTooltip
id="accept-all-changes"
description={t('accept_selected_changes')}
>
<button
className="review-tooltip-menu-button"
onClick={acceptChangesHandler}
aria-label={t('accept_selected_changes')}
<button
className="review-tooltip-menu-button review-tooltip-add-comment-button"
onClick={onAddComment}
>
<MaterialIcon type="chat" />
{t('add_comment')}
</button>
{showChangesButtons && (
<>
<div className="review-tooltip-menu-divider" />
<OLTooltip
id="accept-all-changes"
description={t('accept_selected_changes')}
>
<MaterialIcon type="check" />
</button>
</OLTooltip>
<button
className="review-tooltip-menu-button"
onClick={acceptChangesHandler}
aria-label={t('accept_selected_changes')}
>
<MaterialIcon type="check" />
</button>
</OLTooltip>
<OLTooltip
id="reject-all-changes"
description={t('reject_selected_changes')}
>
<button
className="review-tooltip-menu-button"
onClick={rejectChangesHandler}
aria-label={t('reject_selected_changes')}
<OLTooltip
id="reject-all-changes"
description={t('reject_selected_changes')}
>
<MaterialIcon type="clear" />
</button>
</OLTooltip>
</>
)}
</div>
)
}
<button
className="review-tooltip-menu-button"
onClick={rejectChangesHandler}
aria-label={t('reject_selected_changes')}
>
<MaterialIcon type="clear" />
</button>
</OLTooltip>
</>
)}
</div>
)
}
)
export default ReviewTooltipMenu

View File

@@ -1,5 +1,5 @@
import classNames from 'classnames'
import React from 'react'
import React, { memo } from 'react'
import unfilledIconTypes from '../../../fonts/material-symbols/unfilled-symbols.mjs'
export type AvailableUnfilledIcon = (typeof unfilledIconTypes)[number]
@@ -53,4 +53,4 @@ function MaterialIcon({
)
}
export default MaterialIcon
export default memo(MaterialIcon)