Преглед изворни кода

Merge pull request #12999 from overleaf/ii-history-react-file-tree-optimisation

[web] History file tree list items optimisation

GitOrigin-RevId: b5e9b72b7df857479db3b2a276e6c064d8f9427a
ilkin-overleaf пре 3 година
родитељ
комит
aea43cee29

+ 14 - 9
services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx

@@ -1,29 +1,32 @@
+import { memo } from 'react'
 import classNames from 'classnames'
 import HistoryFileTreeItem from './history-file-tree-item'
 import iconTypeFromName from '../../../file-tree/util/icon-type-from-name'
 import Icon from '../../../../shared/components/icon'
-import { useFileTreeItemSelection } from '../../context/hooks/use-file-tree-item-selection'
 import type { FileDiff } from '../../services/types/file'
 
 type HistoryFileTreeDocProps = {
   file: FileDiff
   name: string
+  selected: boolean
+  onClick: (file: FileDiff) => void
+  onKeyDown: (file: FileDiff, event: React.KeyboardEvent<HTMLLIElement>) => void
 }
 
-export default function HistoryFileTreeDoc({
+function HistoryFileTreeDoc({
   file,
   name,
+  selected,
+  onClick,
+  onKeyDown,
 }: HistoryFileTreeDocProps) {
-  const { isSelected, handleClick, handleKeyDown } =
-    useFileTreeItemSelection(file)
-
   return (
     <li
       role="treeitem"
-      className={classNames({ selected: isSelected })}
-      onClick={handleClick}
-      onKeyDown={handleKeyDown}
-      aria-selected={isSelected}
+      className={classNames({ selected })}
+      onClick={() => onClick(file)}
+      onKeyDown={e => onKeyDown(file, e)}
+      aria-selected={selected}
       aria-label={name}
       tabIndex={0}
     >
@@ -41,3 +44,5 @@ export default function HistoryFileTreeDoc({
     </li>
   )
 }
+
+export default memo(HistoryFileTreeDoc)

+ 48 - 3
services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx

@@ -2,8 +2,10 @@ import classNames from 'classnames'
 
 import HistoryFileTreeDoc from './history-file-tree-doc'
 import HistoryFileTreeFolder from './history-file-tree-folder'
-import type { ReactNode } from 'react'
+import { ReactNode, useCallback } from 'react'
 import type { HistoryFileTree, HistoryDoc } from '../../utils/file-tree'
+import { useHistoryContext } from '../../context/history-context'
+import { FileDiff } from '../../services/types/file'
 
 type HistoryFileTreeFolderListProps = {
   folders: HistoryFileTree[]
@@ -12,12 +14,46 @@ type HistoryFileTreeFolderListProps = {
   children?: ReactNode
 }
 
-export default function HistoryFileTreeFolderList({
+function HistoryFileTreeFolderList({
   folders,
   docs,
   rootClassName,
   children,
 }: HistoryFileTreeFolderListProps) {
+  const { selection, setSelection } = useHistoryContext()
+
+  const handleEvent = useCallback(
+    (file: FileDiff) => {
+      setSelection(prevSelection => {
+        if (file.pathname !== prevSelection.selectedFile?.pathname) {
+          return {
+            ...prevSelection,
+            selectedFile: file,
+          }
+        }
+
+        return prevSelection
+      })
+    },
+    [setSelection]
+  )
+
+  const handleClick = useCallback(
+    (file: FileDiff) => {
+      handleEvent(file)
+    },
+    [handleEvent]
+  )
+
+  const handleKeyDown = useCallback(
+    (file: FileDiff, event: React.KeyboardEvent<HTMLLIElement>) => {
+      if (event.key === 'Enter' || event.key === ' ') {
+        handleEvent(file)
+      }
+    },
+    [handleEvent]
+  )
+
   return (
     <ul className={classNames('list-unstyled', rootClassName)} role="tree">
       {folders.map(folder => (
@@ -29,9 +65,18 @@ export default function HistoryFileTreeFolderList({
         />
       ))}
       {docs.map(doc => (
-        <HistoryFileTreeDoc key={doc.pathname} name={doc.name} file={doc} />
+        <HistoryFileTreeDoc
+          key={doc.pathname}
+          name={doc.name}
+          file={doc}
+          selected={selection.selectedFile?.pathname === doc.pathname}
+          onClick={handleClick}
+          onKeyDown={handleKeyDown}
+        />
       ))}
       {children}
     </ul>
   )
 }
+
+export default HistoryFileTreeFolderList

+ 4 - 2
services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx

@@ -1,4 +1,4 @@
-import { useState } from 'react'
+import { useState, memo } from 'react'
 import { useTranslation } from 'react-i18next'
 
 import HistoryFileTreeItem from './history-file-tree-item'
@@ -13,7 +13,7 @@ type HistoryFileTreeFolderProps = {
   docs: HistoryDoc[]
 }
 
-export default function HistoryFileTreeFolder({
+function HistoryFileTreeFolder({
   name,
   folders,
   docs,
@@ -61,3 +61,5 @@ export default function HistoryFileTreeFolder({
     </>
   )
 }
+
+export default memo(HistoryFileTreeFolder)

+ 18 - 4
services/web/frontend/js/features/history/components/history-file-tree.tsx

@@ -1,3 +1,4 @@
+import { useMemo } from 'react'
 import { orderBy, reduce } from 'lodash'
 import { useHistoryContext } from '../context/history-context'
 import {
@@ -9,13 +10,26 @@ import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list
 export default function HistoryFileTree() {
   const { selection, error } = useHistoryContext()
 
-  const fileTree = reduce(selection.files, reducePathsToTree, [])
+  const fileTree = useMemo(
+    () => reduce(selection.files, reducePathsToTree, []),
+    [selection.files]
+  )
+
+  const sortedFileTree = useMemo(
+    () => orderBy(fileTree, ['-type', 'operation', 'name']),
+    [fileTree]
+  )
 
-  const sortedFileTree = orderBy(fileTree, ['-type', 'operation', 'name'])
+  const mappedFileTree = useMemo(
+    () => fileTreeDiffToFileTreeData(sortedFileTree),
+    [sortedFileTree]
+  )
 
-  const mappedFileTree = fileTreeDiffToFileTreeData(sortedFileTree)
+  if (error) {
+    return null
+  }
 
-  return error ? null : (
+  return (
     <HistoryFileTreeFolderList
       folders={mappedFileTree.folders}
       docs={mappedFileTree.docs ?? []}

+ 0 - 33
services/web/frontend/js/features/history/context/hooks/use-file-tree-item-selection.tsx

@@ -1,33 +0,0 @@
-import { useCallback } from 'react'
-import { useHistoryContext } from '../history-context'
-import type { FileDiff } from '../../services/types/file'
-
-export function useFileTreeItemSelection(file: FileDiff) {
-  const { selection, setSelection } = useHistoryContext()
-
-  const handleEvent = useCallback(() => {
-    if (file.pathname !== selection.selectedFile?.pathname) {
-      setSelection({
-        ...selection,
-        selectedFile: file,
-      })
-    }
-  }, [file, selection, setSelection])
-
-  const handleClick = useCallback(() => {
-    handleEvent()
-  }, [handleEvent])
-
-  const handleKeyDown = useCallback(
-    event => {
-      if (event.key === 'Enter' || event.key === ' ') {
-        handleEvent()
-      }
-    },
-    [handleEvent]
-  )
-
-  const isSelected = selection.selectedFile?.pathname === file.pathname
-
-  return { isSelected, handleClick, handleKeyDown }
-}