Просмотр исходного кода

Memoize FileTree and outline toggle button components (#16776)

GitOrigin-RevId: 299ed9d568650ce37edba87643112d1cd6d12fd4
Alf Eaton 2 лет назад
Родитель
Сommit
c443322a41

+ 4 - 1
services/web/frontend/js/features/chat/context/chat-context.tsx

@@ -176,7 +176,10 @@ export const ChatContext = createContext<
 >(undefined)
 
 export const ChatProvider: FC = ({ children }) => {
-  const clientId = useRef(uuid())
+  const clientId = useRef<string>()
+  if (clientId.current === undefined) {
+    clientId.current = uuid()
+  }
   const user = useUserContext()
   const { _id: projectId } = useProjectContext()
 

+ 3 - 3
services/web/frontend/js/features/ide-react/components/file-tree.tsx

@@ -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 { useReferencesContext } from '@/features/ide-react/context/references-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 { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
 
-export function FileTree() {
+export const FileTree = memo(function FileTree() {
   const user = useUserContext()
   const { indexAllReferences } = useReferencesContext()
   const { setStartedFreeTrial } = useIdeReactContext()
@@ -44,4 +44,4 @@ export function FileTree() {
       />
     </div>
   )
-}
+})

+ 24 - 0
services/web/frontend/js/features/outline/components/outline-item-toggle-button.tsx

@@ -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'

+ 7 - 20
services/web/frontend/js/features/outline/components/outline-item.jsx

@@ -2,9 +2,8 @@ import { useState, useEffect, useRef, memo } from 'react'
 import PropTypes from 'prop-types'
 import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
 import classNames from 'classnames'
-import { useTranslation } from 'react-i18next'
 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({
   outlineItem,
@@ -13,8 +12,6 @@ const OutlineItem = memo(function OutlineItem({
   matchesHighlightedLine,
   containsHighlightedLine,
 }) {
-  const { t } = useTranslation()
-
   const [expanded, setExpanded] = useState(true)
   const titleElementRef = useRef()
   const isHighlightedRef = useRef(false)
@@ -30,10 +27,6 @@ const OutlineItem = memo(function OutlineItem({
     'outline-item-link-highlight': isHighlighted,
   })
 
-  function handleExpandCollapseClick() {
-    setExpanded(!expanded)
-  }
-
   function handleOutlineItemLinkClick(event) {
     const syncToPdf = event.detail === 2 // double-click = sync to PDF
     jumpToLine(outlineItem.line, syncToPdf)
@@ -65,18 +58,12 @@ const OutlineItem = memo(function OutlineItem({
       aria-label={outlineItem.title}
     >
       <div className="outline-item-row">
-        {outlineItem.children ? (
-          <button
-            className="outline-item-expand-collapse-btn"
-            onClick={handleExpandCollapseClick}
-            aria-label={expanded ? t('collapse') : t('expand')}
-          >
-            <Icon
-              type={expanded ? 'angle-down' : 'angle-right'}
-              className="outline-caret-icon"
-            />
-          </button>
-        ) : null}
+        {!!outlineItem.children && (
+          <OutlineItemToggleButton
+            expanded={expanded}
+            setExpanded={setExpanded}
+          />
+        )}
         <button
           className={itemLinkClasses}
           onClick={handleOutlineItemLinkClick}

+ 8 - 31
services/web/frontend/js/features/outline/components/outline-pane.tsx

@@ -1,11 +1,9 @@
 import React, { useEffect } from 'react'
 import classNames from 'classnames'
-import { useTranslation } from 'react-i18next'
 
 import OutlineRoot from './outline-root'
-import Icon from '../../../shared/components/icon'
 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<{
   isTexFile: boolean
@@ -28,8 +26,6 @@ const OutlinePane = React.memo<{
   expanded,
   toggleExpanded,
 }) {
-  const { t } = useTranslation()
-
   const isOpen = Boolean(isTexFile && expanded)
 
   useEffect(() => {
@@ -43,32 +39,13 @@ const OutlinePane = React.memo<{
   return (
     <div className={headerClasses}>
       <header className="outline-header">
-        <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
+          toggleExpanded={toggleExpanded}
+          expanded={expanded}
+          isOpen={isOpen}
+          isPartial={isPartial}
+          isTexFile={isTexFile}
+        />
       </header>
       {isOpen && (
         <div className="outline-body">

+ 44 - 0
services/web/frontend/js/features/outline/components/outline-toggle-button.tsx

@@ -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'

+ 2 - 2
services/web/frontend/js/shared/context/user-context.tsx

@@ -1,11 +1,11 @@
-import { createContext, FC, useContext } from 'react'
+import { createContext, FC, useContext, useMemo } from 'react'
 import getMeta from '../../utils/meta'
 import { User } from '../../../../types/user'
 
 export const UserContext = createContext<User | undefined>(undefined)
 
 export const UserProvider: FC = ({ children }) => {
-  const user = getMeta('ol-user')
+  const user = useMemo(() => getMeta('ol-user'), [])
 
   return <UserContext.Provider value={user}>{children}</UserContext.Provider>
 }

+ 1 - 1
services/web/frontend/js/shared/hooks/use-browser-window.ts

@@ -43,7 +43,7 @@ function setTitle(title: string) {
 }
 
 function useBrowserWindow() {
-  const [hasFocus, setHasFocus] = useState(document.hasFocus())
+  const [hasFocus, setHasFocus] = useState(() => document.hasFocus())
 
   useEffect(() => {
     function handleFocusEvent() {