Merge pull request #10397 from overleaf/td-memoize-file-outline

Memoize file outline

GitOrigin-RevId: cb086bab2b6ead251362180d776e7eaff18fc639
This commit is contained in:
Mathias Jakobsen 2022-11-14 09:42:06 +00:00 committed by Copybot
parent 6ae22ff596
commit 81e2265e72
4 changed files with 57 additions and 21 deletions

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'
import { useState, useEffect, useRef, memo } from 'react'
import PropTypes from 'prop-types'
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
import classNames from 'classnames'
@ -6,15 +6,13 @@ import { useTranslation } from 'react-i18next'
import OutlineList from './outline-list'
import Icon from '../../../shared/components/icon'
function getChildrenLines(children) {
return (children || [])
.map(child => {
return getChildrenLines(child.children).concat(child.line)
})
.flat()
}
function OutlineItem({ outlineItem, jumpToLine, highlightedLine }) {
const OutlineItem = memo(function OutlineItem({
outlineItem,
jumpToLine,
highlightedLine,
matchesHighlightedLine,
containsHighlightedLine,
}) {
const { t } = useTranslation()
const [expanded, setExpanded] = useState(true)
@ -25,12 +23,8 @@ function OutlineItem({ outlineItem, jumpToLine, highlightedLine }) {
'outline-item-no-children': !outlineItem.children,
})
const hasHighlightedChild =
!expanded &&
getChildrenLines(outlineItem.children).includes(highlightedLine)
const isHighlighted =
highlightedLine === outlineItem.line || hasHighlightedChild
const hasHighlightedChild = !expanded && containsHighlightedLine
const isHighlighted = matchesHighlightedLine || hasHighlightedChild
const itemLinkClasses = classNames('outline-item-link', {
'outline-item-link-highlight': isHighlighted,
@ -90,16 +84,21 @@ function OutlineItem({ outlineItem, jumpToLine, highlightedLine }) {
</button>
</div>
{expanded && outlineItem.children ? (
// highlightedLine is only provided to this list if the list contains
// the highlighted line. This means that whenever the list does not
// contain the highlighted line, the props provided to it are the same
// and the component can be memoized.
<OutlineList
outline={outlineItem.children}
jumpToLine={jumpToLine}
isRoot={false}
highlightedLine={highlightedLine}
highlightedLine={containsHighlightedLine ? highlightedLine : null}
containsHighlightedLine={containsHighlightedLine}
/>
) : null}
</li>
)
}
})
OutlineItem.propTypes = {
outlineItem: PropTypes.exact({
@ -113,6 +112,8 @@ OutlineItem.propTypes = {
}).isRequired,
jumpToLine: PropTypes.func.isRequired,
highlightedLine: PropTypes.number,
matchesHighlightedLine: PropTypes.bool,
containsHighlightedLine: PropTypes.bool,
}
export default OutlineItem

View file

@ -1,32 +1,64 @@
import PropTypes from 'prop-types'
import classNames from 'classnames'
import OutlineItem from './outline-item'
import { memo } from 'react'
function OutlineList({ outline, jumpToLine, isRoot, highlightedLine }) {
function getChildrenLines(children) {
return (children || [])
.map(child => {
return getChildrenLines(child.children).concat(child.line)
})
.flat()
}
const OutlineList = memo(function OutlineList({
outline,
jumpToLine,
isRoot,
highlightedLine,
containsHighlightedLine,
}) {
const listClasses = classNames('outline-item-list', {
'outline-item-list-root': isRoot,
})
return (
<ul className={listClasses} role={isRoot ? 'tree' : 'group'}>
{outline.map((outlineItem, idx) => {
const matchesHighlightedLine =
containsHighlightedLine && highlightedLine === outlineItem.line
const itemContainsHighlightedLine =
containsHighlightedLine &&
getChildrenLines(outlineItem.children).includes(highlightedLine)
// highlightedLine is only provided to the item if the item matches or
// contains the highlighted line. This means that whenever the item does
// not contain the highlighted line, the props provided to it are the
// same and the component can be memoized.
return (
<OutlineItem
key={`${outlineItem.level}-${idx}`}
outlineItem={outlineItem}
jumpToLine={jumpToLine}
highlightedLine={highlightedLine}
highlightedLine={
matchesHighlightedLine || itemContainsHighlightedLine
? highlightedLine
: null
}
matchesHighlightedLine={matchesHighlightedLine}
containsHighlightedLine={itemContainsHighlightedLine}
/>
)
})}
</ul>
)
}
})
OutlineList.propTypes = {
outline: PropTypes.array.isRequired,
jumpToLine: PropTypes.func.isRequired,
isRoot: PropTypes.bool,
highlightedLine: PropTypes.number,
containsHighlightedLine: PropTypes.bool,
}
export default OutlineList

View file

@ -14,6 +14,7 @@ function OutlineRoot({ outline, jumpToLine, highlightedLine }) {
jumpToLine={jumpToLine}
isRoot
highlightedLine={highlightedLine}
containsHighlightedLine
/>
) : (
<div className="outline-body-no-elements">

View file

@ -54,6 +54,7 @@ describe('<OutlineItem />', function () {
outlineItem={outlineItem}
jumpToLine={jumpToLine}
highlightedLine={1}
matchesHighlightedLine
/>
)
@ -71,6 +72,7 @@ describe('<OutlineItem />', function () {
outlineItem={outlineItem}
jumpToLine={jumpToLine}
highlightedLine={2}
containsHighlightedLine
/>
)