From 9a55bbf325e9727f363586b8428a213a5ff75c71 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Mon, 3 Apr 2023 09:40:32 -0700 Subject: [PATCH] Port editor react file tree to history file tree (#12453) This new history file tree is mostly copied from the editor file tree, with some of the features stripped away: 1. Remove multiple selections 2. Remove drag and drop ability 3. Remove the ability to rename files & folders 4. No more right-click hijacking (context menu) 5. No more triple dots menu on a file tree item shown 6. No file references, since history doesn't have the data to differentiate between real files and linked file 7. etc (some other small changes that are not too important to be listed) Other notable changes: 1. Simplify the selectable provider (the only context provider being copied from react file tree) 2. Convert to typescript GitOrigin-RevId: 1017e545b2bd99775e01307a9b7eac2daf454014 --- .../file-tree/history-file-tree-doc.tsx | 36 ++++++++ .../history-file-tree-folder-list.tsx | 48 ++++++++++ .../file-tree/history-file-tree-folder.tsx | 63 ++++++++++++++ .../file-tree/history-file-tree-item.tsx | 19 ++++ .../history/components/history-file-tree.tsx | 47 ++++------ .../history/components/history-root.tsx | 11 +-- .../context/history-file-tree-selectable.tsx | 87 +++++++++++++++++++ .../history-file-tree-controller.js | 8 +- .../js/features/history/utils/file-tree.ts | 12 +-- 9 files changed, 277 insertions(+), 54 deletions(-) create mode 100644 services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx create mode 100644 services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx create mode 100644 services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx create mode 100644 services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx create mode 100644 services/web/frontend/js/features/history/context/history-file-tree-selectable.tsx diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx new file mode 100644 index 0000000000..b2c027e499 --- /dev/null +++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx @@ -0,0 +1,36 @@ +import HistoryFileTreeItem from './history-file-tree-item' +import iconTypeFromName from '../../../file-tree/util/icon-type-from-name' +import Icon from '../../../../shared/components/icon' +import { useSelectableEntity } from '../../context/history-file-tree-selectable' + +type HistoryFileTreeDocProps = { + name: string + id: string +} + +export default function HistoryFileTreeDoc({ + name, + id, +}: HistoryFileTreeDocProps) { + const { props: selectableEntityProps } = useSelectableEntity(id) + + return ( +
  • + + } + /> +
  • + ) +} diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx new file mode 100644 index 0000000000..a7541b15c4 --- /dev/null +++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx @@ -0,0 +1,48 @@ +import classNames from 'classnames' + +import HistoryFileTreeDoc from './history-file-tree-doc' +import HistoryFileTreeFolder from './history-file-tree-folder' +import { fileCollator } from '../../../file-tree/util/file-collator' +import type { Doc } from '../../../../../../types/doc' +import type { ReactNode } from 'react' +import type { HistoryFileTree } from '../../utils/file-tree' + +type HistoryFileTreeFolderListProps = { + folders: HistoryFileTree[] + docs: Doc[] + rootClassName?: string + children?: ReactNode +} + +export default function HistoryFileTreeFolderList({ + folders, + docs, + rootClassName, + children, +}: HistoryFileTreeFolderListProps) { + return ( + + ) +} + +function compareFunction( + one: HistoryFileTree | Doc, + two: HistoryFileTree | Doc +) { + return fileCollator.compare(one.name, two.name) +} diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx new file mode 100644 index 0000000000..4307521836 --- /dev/null +++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx @@ -0,0 +1,63 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import HistoryFileTreeItem from './history-file-tree-item' +import HistoryFileTreeFolderList from './history-file-tree-folder-list' + +import Icon from '../../../../shared/components/icon' +import type { Doc } from '../../../../../../types/doc' +import type { HistoryFileTree } from '../../utils/file-tree' + +type HistoryFileTreeFolderProps = { + name: string + folders: HistoryFileTree[] + docs: Doc[] +} + +export default function HistoryFileTreeFolder({ + name, + folders, + docs, +}: HistoryFileTreeFolderProps) { + const { t } = useTranslation() + + const [expanded, setExpanded] = useState(true) + + const icons = ( + <> + + + + ) + + return ( + <> +
  • setExpanded(!expanded)} + onKeyDown={() => setExpanded(!expanded)} + > + +
  • + {expanded ? ( + + ) : null} + + ) +} diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx new file mode 100644 index 0000000000..af5a7b94df --- /dev/null +++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react' + +type FileTreeItemProps = { + name: string + icons: ReactNode +} + +export default function FileTreeItem({ name, icons }: FileTreeItemProps) { + return ( +
    +
    + {icons} + +
    +
    + ) +} diff --git a/services/web/frontend/js/features/history/components/history-file-tree.tsx b/services/web/frontend/js/features/history/components/history-file-tree.tsx index 5aecbeb149..b8ebae1486 100644 --- a/services/web/frontend/js/features/history/components/history-file-tree.tsx +++ b/services/web/frontend/js/features/history/components/history-file-tree.tsx @@ -1,28 +1,24 @@ import _ from 'lodash' -import FileTreeContext from '../../file-tree/components/file-tree-context' -import FileTreeFolderList from '../../file-tree/components/file-tree-folder-list' +import { useCallback } from 'react' import { useHistoryContext } from '../context/history-context' +import { HistoryFileTreeSelectableProvider } from '../context/history-file-tree-selectable' import { fileTreeDiffToFileTreeData, reducePathsToTree, } from '../utils/file-tree' +import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list' -type HistoryFileTreeProps = { - setRefProviderEnabled: any - setStartedFreeTrial: any - reindexReferences: any - onSelect: any - refProviders: any -} +export default function HistoryFileTree() { + const { fileSelection, setFileSelection } = useHistoryContext() -export default function HistoryFileTree({ - setRefProviderEnabled, - setStartedFreeTrial, - reindexReferences, - onSelect, - refProviders, -}: HistoryFileTreeProps) { - const { fileSelection } = useHistoryContext() + const handleSelectFile = useCallback( + (pathname: string) => { + if (fileSelection) { + setFileSelection({ files: fileSelection.files, pathname }) + } + }, + [fileSelection, setFileSelection] + ) if (!fileSelection) { return null @@ -33,21 +29,14 @@ export default function HistoryFileTree({ const mappedFileTree = fileTreeDiffToFileTreeData(fileTree) return ( - - +
  • - - + + ) } diff --git a/services/web/frontend/js/features/history/components/history-root.tsx b/services/web/frontend/js/features/history/components/history-root.tsx index f0ba4c5a1d..4cc81c11ac 100644 --- a/services/web/frontend/js/features/history/components/history-root.tsx +++ b/services/web/frontend/js/features/history/components/history-root.tsx @@ -19,16 +19,7 @@ function Main() { return ( <> {fileTreeContainer - ? createPortal( - {}} - refProviders={{}} - reindexReferences={() => {}} - setRefProviderEnabled={() => {}} - setStartedFreeTrial={() => {}} - />, - fileTreeContainer - ) + ? createPortal(, fileTreeContainer) : null}
    diff --git a/services/web/frontend/js/features/history/context/history-file-tree-selectable.tsx b/services/web/frontend/js/features/history/context/history-file-tree-selectable.tsx new file mode 100644 index 0000000000..a4dd5ad555 --- /dev/null +++ b/services/web/frontend/js/features/history/context/history-file-tree-selectable.tsx @@ -0,0 +1,87 @@ +import { + createContext, + type ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react' +import classNames from 'classnames' +import _ from 'lodash' + +import usePreviousValue from '../../../shared/hooks/use-previous-value' +import { Nullable } from '../../../../../types/utils' + +type Context = { + select: (id: string) => void + selectedFile: Nullable +} + +const FileTreeSelectableContext = createContext({ + select: () => {}, + selectedFile: null, +}) + +type HistoryFileTreeSelectableProviderProps = { + onSelectFile: (id: string) => void + children: ReactNode +} + +export function HistoryFileTreeSelectableProvider({ + onSelectFile, + children, +}: HistoryFileTreeSelectableProviderProps) { + const [selectedFile, setSelectedFile] = + useState(null) + + const previousSelectedFile = usePreviousValue(selectedFile) + + useEffect(() => { + if (!selectedFile) { + return + } + + if (_.isEqual(selectedFile, previousSelectedFile)) { + return + } + + onSelectFile(selectedFile) + }, [selectedFile, previousSelectedFile, onSelectFile]) + + const select = useCallback(id => { + setSelectedFile(id) + }, []) + + const value = { + selectedFile, + select, + } + + return ( + + {children} + + ) +} + +export function useSelectableEntity(id: string) { + const { selectedFile, select } = useContext(FileTreeSelectableContext) + + const handleClick = useCallback(() => { + select(id) + }, [id, select]) + + const isSelected = selectedFile === id + + const props = useMemo( + () => ({ + className: classNames({ selected: isSelected }), + 'aria-selected': isSelected, + onClick: handleClick, + }), + [handleClick, isSelected] + ) + + return { isSelected, props } +} diff --git a/services/web/frontend/js/features/history/controllers/history-file-tree-controller.js b/services/web/frontend/js/features/history/controllers/history-file-tree-controller.js index 2775e66e8d..36c72a22f4 100644 --- a/services/web/frontend/js/features/history/controllers/history-file-tree-controller.js +++ b/services/web/frontend/js/features/history/controllers/history-file-tree-controller.js @@ -5,11 +5,5 @@ import HistoryFileTree from '../components/history-file-tree' App.component( 'historyFileTreeReact', - react2angular(rootContext.use(HistoryFileTree), [ - 'refProviders', - 'setRefProviderEnabled', - 'setStartedFreeTrial', - 'reindexReferences', - 'onSelect', - ]) + react2angular(rootContext.use(HistoryFileTree)) ) diff --git a/services/web/frontend/js/features/history/utils/file-tree.ts b/services/web/frontend/js/features/history/utils/file-tree.ts index bea2596228..ff125534df 100644 --- a/services/web/frontend/js/features/history/utils/file-tree.ts +++ b/services/web/frontend/js/features/history/utils/file-tree.ts @@ -48,14 +48,11 @@ export function reducePathsToTree( return currentFileTree } -type HistoryFileTree = { +export type HistoryFileTree = { docs?: Doc[] folders: HistoryFileTree[] name: string - // `id` and `fileRefs` are both required from react file tree. - // TODO: update react file tree to make the data optional so we can delete these keys - id: '' - fileRefs: [] + _id: string } export function fileTreeDiffToFileTreeData( @@ -68,7 +65,7 @@ export function fileTreeDiffToFileTreeData( for (const file of fileTreeDiff) { if (file.type === 'file') { docs.push({ - _id: '', + _id: file.pathname as string, name: file.name ?? '', }) } else if (file.type === 'folder') { @@ -83,8 +80,7 @@ export function fileTreeDiffToFileTreeData( docs, folders, name: currentFolderName, - id: '', - fileRefs: [], + _id: currentFolderName, } }