mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Avoid using isEqual for outline comparison (#16093)
GitOrigin-RevId: 8901f77eb25295882f6563fa2f1835d18d332c59
This commit is contained in:
parent
c4b05f7a73
commit
3f98752986
2 changed files with 65 additions and 49 deletions
|
@ -1,16 +1,15 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useCodeMirrorStateContext } from './codemirror-editor'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import OutlinePane from '../../outline/components/outline-pane'
|
||||
import { documentOutline } from '../languages/latex/document-outline'
|
||||
import isValidTeXFile from '../../../main/is-valid-tex-file'
|
||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import useScopeEventEmitter from '../../../shared/hooks/use-scope-event-emitter'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { nestOutline } from '../utils/tree-query'
|
||||
import { nestOutline, Outline } from '../utils/tree-query'
|
||||
import { ProjectionStatus } from '../utils/tree-operations/projection'
|
||||
import useEventListener from '../../../shared/hooks/use-event-listener'
|
||||
import useDeepCompareMemo from '../../../shared/hooks/use-deep-compare-memo'
|
||||
import useDebounce from '../../../shared/hooks/use-debounce'
|
||||
|
||||
const closestSectionLineNumber = (
|
||||
|
@ -30,6 +29,39 @@ const closestSectionLineNumber = (
|
|||
return highestLine
|
||||
}
|
||||
|
||||
type PartialFlatOutline = {
|
||||
level: number
|
||||
title: string
|
||||
line: number
|
||||
}[]
|
||||
|
||||
const outlineChanged = (
|
||||
a: PartialFlatOutline | undefined,
|
||||
b: PartialFlatOutline
|
||||
): boolean => {
|
||||
if (!a) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (a.length !== b.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const aItem = a[i]
|
||||
const bItem = b[i]
|
||||
if (
|
||||
aItem.level !== bItem.level ||
|
||||
aItem.line !== bItem.line ||
|
||||
aItem.title !== bItem.title
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
|
||||
const state = useCodeMirrorStateContext()
|
||||
const debouncedState = useDebounce(state, 100)
|
||||
|
@ -90,28 +122,37 @@ export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
|
|||
}, [])
|
||||
)
|
||||
|
||||
const outlineStatus = useMemo(
|
||||
() =>
|
||||
debouncedState.field(documentOutline, false)?.status ||
|
||||
ProjectionStatus.Pending,
|
||||
[debouncedState]
|
||||
)
|
||||
const outlineResult = debouncedState.field(documentOutline, false)
|
||||
|
||||
const flatOutline = useMemo(() => {
|
||||
const outlineResult = debouncedState.field(documentOutline, false)
|
||||
if (outlineResult?.status !== ProjectionStatus.Pending) {
|
||||
// We have a (potentially partial) outline.
|
||||
return outlineResult?.items!.map(element => {
|
||||
// Remove {from, to} to not trip up deep comparison
|
||||
const { level, title, line } = element
|
||||
return { level, title, line }
|
||||
})
|
||||
// when the outline projection changes, calculate the flat outline
|
||||
const flatOutline = useMemo<PartialFlatOutline | undefined>(() => {
|
||||
if (!outlineResult || outlineResult.status === ProjectionStatus.Pending) {
|
||||
return undefined
|
||||
}
|
||||
return undefined
|
||||
}, [debouncedState])
|
||||
|
||||
const outline = useDeepCompareMemo(() => {
|
||||
return flatOutline ? nestOutline(flatOutline) : []
|
||||
// We have a (potentially partial) outline.
|
||||
return outlineResult.items.map(element => {
|
||||
const { level, title, line } = element
|
||||
return { level, title, line }
|
||||
})
|
||||
}, [outlineResult])
|
||||
|
||||
const [outline, setOutline] = useState<Outline[]>([])
|
||||
|
||||
const prevFlatOutlineRef = useRef<PartialFlatOutline | undefined>(undefined)
|
||||
|
||||
// when the flat outline changes, calculate the nested outline
|
||||
useEffect(() => {
|
||||
const prevFlatOutline = prevFlatOutlineRef.current
|
||||
prevFlatOutlineRef.current = flatOutline
|
||||
|
||||
if (flatOutline) {
|
||||
if (outlineChanged(prevFlatOutline, flatOutline)) {
|
||||
setOutline(nestOutline(flatOutline))
|
||||
}
|
||||
} else {
|
||||
setOutline([])
|
||||
}
|
||||
}, [flatOutline])
|
||||
|
||||
const jumpToLine = useCallback(
|
||||
|
@ -123,13 +164,6 @@ export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
|
|||
[goToLineEmitter]
|
||||
)
|
||||
|
||||
const onToggle = useCallback(
|
||||
isOpen => {
|
||||
outlineToggledEmitter(isOpen)
|
||||
},
|
||||
[outlineToggledEmitter]
|
||||
)
|
||||
|
||||
const highlightedLine = useMemo(
|
||||
() => closestSectionLineNumber(flatOutline, currentlyHighlightedLine),
|
||||
[flatOutline, currentlyHighlightedLine]
|
||||
|
@ -143,13 +177,13 @@ export const CodemirrorOutline = React.memo(function CodemirrorOutline() {
|
|||
return createPortal(
|
||||
<OutlinePane
|
||||
outline={outline}
|
||||
onToggle={onToggle}
|
||||
onToggle={outlineToggledEmitter}
|
||||
eventTracking={eventTracking}
|
||||
isTexFile={isTexFile && !binaryFileOpened}
|
||||
jumpToLine={jumpToLine}
|
||||
highlightedLine={highlightedLine}
|
||||
show
|
||||
isPartial={outlineStatus === ProjectionStatus.Partial}
|
||||
isPartial={outlineResult?.status === ProjectionStatus.Partial}
|
||||
/>,
|
||||
outlineDomElement
|
||||
)
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import { DependencyList, useMemo, useRef } from 'react'
|
||||
import { isEqual } from 'lodash'
|
||||
|
||||
function useDeepCompare(dependencies: DependencyList) {
|
||||
const ref = useRef<DependencyList>([])
|
||||
if (!isEqual(ref.current, dependencies)) {
|
||||
ref.current = dependencies
|
||||
}
|
||||
return ref.current
|
||||
}
|
||||
|
||||
export default function useDeepCompareMemo<T>(
|
||||
factory: () => T,
|
||||
dependencies: DependencyList
|
||||
) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
return useMemo(factory, useDeepCompare(dependencies))
|
||||
}
|
Loading…
Reference in a new issue