mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Memoize FileTree and outline toggle button components (#16776)
GitOrigin-RevId: 299ed9d568650ce37edba87643112d1cd6d12fd4
This commit is contained in:
parent
f06f2ef99e
commit
c443322a41
8 changed files with 93 additions and 58 deletions
|
@ -176,7 +176,10 @@ export const ChatContext = createContext<
|
||||||
>(undefined)
|
>(undefined)
|
||||||
|
|
||||||
export const ChatProvider: FC = ({ children }) => {
|
export const ChatProvider: FC = ({ children }) => {
|
||||||
const clientId = useRef(uuid())
|
const clientId = useRef<string>()
|
||||||
|
if (clientId.current === undefined) {
|
||||||
|
clientId.current = uuid()
|
||||||
|
}
|
||||||
const user = useUserContext()
|
const user = useUserContext()
|
||||||
const { _id: projectId } = useProjectContext()
|
const { _id: projectId } = useProjectContext()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { memo, useCallback, useState } from 'react'
|
||||||
import { useUserContext } from '@/shared/context/user-context'
|
import { useUserContext } from '@/shared/context/user-context'
|
||||||
import { useReferencesContext } from '@/features/ide-react/context/references-context'
|
import { useReferencesContext } from '@/features/ide-react/context/references-context'
|
||||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||||
|
@ -7,7 +7,7 @@ import { RefProviders } from '../../../../../types/user'
|
||||||
import FileTreeRoot from '@/features/file-tree/components/file-tree-root'
|
import FileTreeRoot from '@/features/file-tree/components/file-tree-root'
|
||||||
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
||||||
|
|
||||||
export function FileTree() {
|
export const FileTree = memo(function FileTree() {
|
||||||
const user = useUserContext()
|
const user = useUserContext()
|
||||||
const { indexAllReferences } = useReferencesContext()
|
const { indexAllReferences } = useReferencesContext()
|
||||||
const { setStartedFreeTrial } = useIdeReactContext()
|
const { setStartedFreeTrial } = useIdeReactContext()
|
||||||
|
@ -44,4 +44,4 @@ export function FileTree() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { memo, type Dispatch, type SetStateAction } from 'react'
|
||||||
|
import Icon from '@/shared/components/icon'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export const OutlineItemToggleButton = memo<{
|
||||||
|
expanded: boolean
|
||||||
|
setExpanded: Dispatch<SetStateAction<boolean>>
|
||||||
|
}>(({ expanded, setExpanded }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="outline-item-expand-collapse-btn"
|
||||||
|
onClick={() => setExpanded(value => !value)}
|
||||||
|
aria-label={expanded ? t('collapse') : t('expand')}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
type={expanded ? 'angle-down' : 'angle-right'}
|
||||||
|
className="outline-caret-icon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
OutlineItemToggleButton.displayName = 'OutlineItemToggleButton'
|
|
@ -2,9 +2,8 @@ import { useState, useEffect, useRef, memo } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
|
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import OutlineList from './outline-list'
|
import OutlineList from './outline-list'
|
||||||
import Icon from '../../../shared/components/icon'
|
import { OutlineItemToggleButton } from '@/features/outline/components/outline-item-toggle-button'
|
||||||
|
|
||||||
const OutlineItem = memo(function OutlineItem({
|
const OutlineItem = memo(function OutlineItem({
|
||||||
outlineItem,
|
outlineItem,
|
||||||
|
@ -13,8 +12,6 @@ const OutlineItem = memo(function OutlineItem({
|
||||||
matchesHighlightedLine,
|
matchesHighlightedLine,
|
||||||
containsHighlightedLine,
|
containsHighlightedLine,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(true)
|
const [expanded, setExpanded] = useState(true)
|
||||||
const titleElementRef = useRef()
|
const titleElementRef = useRef()
|
||||||
const isHighlightedRef = useRef(false)
|
const isHighlightedRef = useRef(false)
|
||||||
|
@ -30,10 +27,6 @@ const OutlineItem = memo(function OutlineItem({
|
||||||
'outline-item-link-highlight': isHighlighted,
|
'outline-item-link-highlight': isHighlighted,
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleExpandCollapseClick() {
|
|
||||||
setExpanded(!expanded)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOutlineItemLinkClick(event) {
|
function handleOutlineItemLinkClick(event) {
|
||||||
const syncToPdf = event.detail === 2 // double-click = sync to PDF
|
const syncToPdf = event.detail === 2 // double-click = sync to PDF
|
||||||
jumpToLine(outlineItem.line, syncToPdf)
|
jumpToLine(outlineItem.line, syncToPdf)
|
||||||
|
@ -65,18 +58,12 @@ const OutlineItem = memo(function OutlineItem({
|
||||||
aria-label={outlineItem.title}
|
aria-label={outlineItem.title}
|
||||||
>
|
>
|
||||||
<div className="outline-item-row">
|
<div className="outline-item-row">
|
||||||
{outlineItem.children ? (
|
{!!outlineItem.children && (
|
||||||
<button
|
<OutlineItemToggleButton
|
||||||
className="outline-item-expand-collapse-btn"
|
expanded={expanded}
|
||||||
onClick={handleExpandCollapseClick}
|
setExpanded={setExpanded}
|
||||||
aria-label={expanded ? t('collapse') : t('expand')}
|
/>
|
||||||
>
|
)}
|
||||||
<Icon
|
|
||||||
type={expanded ? 'angle-down' : 'angle-right'}
|
|
||||||
className="outline-caret-icon"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
<button
|
<button
|
||||||
className={itemLinkClasses}
|
className={itemLinkClasses}
|
||||||
onClick={handleOutlineItemLinkClick}
|
onClick={handleOutlineItemLinkClick}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
import OutlineRoot from './outline-root'
|
import OutlineRoot from './outline-root'
|
||||||
import Icon from '../../../shared/components/icon'
|
|
||||||
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
import { OutlineToggleButton } from '@/features/outline/components/outline-toggle-button'
|
||||||
|
|
||||||
const OutlinePane = React.memo<{
|
const OutlinePane = React.memo<{
|
||||||
isTexFile: boolean
|
isTexFile: boolean
|
||||||
|
@ -28,8 +26,6 @@ const OutlinePane = React.memo<{
|
||||||
expanded,
|
expanded,
|
||||||
toggleExpanded,
|
toggleExpanded,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const isOpen = Boolean(isTexFile && expanded)
|
const isOpen = Boolean(isTexFile && expanded)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -43,32 +39,13 @@ const OutlinePane = React.memo<{
|
||||||
return (
|
return (
|
||||||
<div className={headerClasses}>
|
<div className={headerClasses}>
|
||||||
<header className="outline-header">
|
<header className="outline-header">
|
||||||
<button
|
<OutlineToggleButton
|
||||||
className="outline-header-expand-collapse-btn"
|
toggleExpanded={toggleExpanded}
|
||||||
disabled={!isTexFile}
|
expanded={expanded}
|
||||||
onClick={toggleExpanded}
|
isOpen={isOpen}
|
||||||
aria-label={expanded ? t('hide_outline') : t('show_outline')}
|
isPartial={isPartial}
|
||||||
>
|
isTexFile={isTexFile}
|
||||||
<Icon
|
/>
|
||||||
type={isOpen ? 'angle-down' : 'angle-right'}
|
|
||||||
className="outline-caret-icon"
|
|
||||||
/>
|
|
||||||
<h4 className="outline-header-name">{t('file_outline')}</h4>
|
|
||||||
{isPartial && (
|
|
||||||
<Tooltip
|
|
||||||
id="partial-outline"
|
|
||||||
description={t('partial_outline_warning')}
|
|
||||||
overlayProps={{ placement: 'top' }}
|
|
||||||
>
|
|
||||||
<span role="status">
|
|
||||||
<Icon
|
|
||||||
type="exclamation-triangle"
|
|
||||||
aria-label={t('partial_outline_warning')}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</header>
|
</header>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="outline-body">
|
<div className="outline-body">
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import Icon from '@/shared/components/icon'
|
||||||
|
import Tooltip from '@/shared/components/tooltip'
|
||||||
|
import React, { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export const OutlineToggleButton = memo<{
|
||||||
|
isTexFile: boolean
|
||||||
|
toggleExpanded: () => void
|
||||||
|
expanded?: boolean
|
||||||
|
isOpen: boolean
|
||||||
|
isPartial: boolean
|
||||||
|
}>(({ isTexFile, toggleExpanded, expanded, isOpen, isPartial }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="outline-header-expand-collapse-btn"
|
||||||
|
disabled={!isTexFile}
|
||||||
|
onClick={toggleExpanded}
|
||||||
|
aria-label={expanded ? t('hide_outline') : t('show_outline')}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
type={isOpen ? 'angle-down' : 'angle-right'}
|
||||||
|
className="outline-caret-icon"
|
||||||
|
/>
|
||||||
|
<h4 className="outline-header-name">{t('file_outline')}</h4>
|
||||||
|
{isPartial && (
|
||||||
|
<Tooltip
|
||||||
|
id="partial-outline"
|
||||||
|
description={t('partial_outline_warning')}
|
||||||
|
overlayProps={{ placement: 'top' }}
|
||||||
|
>
|
||||||
|
<span role="status">
|
||||||
|
<Icon
|
||||||
|
type="exclamation-triangle"
|
||||||
|
aria-label={t('partial_outline_warning')}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
OutlineToggleButton.displayName = 'OutlineToggleButton'
|
|
@ -1,11 +1,11 @@
|
||||||
import { createContext, FC, useContext } from 'react'
|
import { createContext, FC, useContext, useMemo } from 'react'
|
||||||
import getMeta from '../../utils/meta'
|
import getMeta from '../../utils/meta'
|
||||||
import { User } from '../../../../types/user'
|
import { User } from '../../../../types/user'
|
||||||
|
|
||||||
export const UserContext = createContext<User | undefined>(undefined)
|
export const UserContext = createContext<User | undefined>(undefined)
|
||||||
|
|
||||||
export const UserProvider: FC = ({ children }) => {
|
export const UserProvider: FC = ({ children }) => {
|
||||||
const user = getMeta('ol-user')
|
const user = useMemo(() => getMeta('ol-user'), [])
|
||||||
|
|
||||||
return <UserContext.Provider value={user}>{children}</UserContext.Provider>
|
return <UserContext.Provider value={user}>{children}</UserContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ function setTitle(title: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function useBrowserWindow() {
|
function useBrowserWindow() {
|
||||||
const [hasFocus, setHasFocus] = useState(document.hasFocus())
|
const [hasFocus, setHasFocus] = useState(() => document.hasFocus())
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleFocusEvent() {
|
function handleFocusEvent() {
|
||||||
|
|
Loading…
Reference in a new issue