Skip to content

Commit

Permalink
perf: ⚡️ 优化文件搜索结果树模式显示渲染;优化搜索逻辑、减少代码冗余。 (#121)
Browse files Browse the repository at this point in the history
* perf: ⚡️ 优化文件搜索结果树模式显示渲染;优化搜索逻辑、减少代码冗余。

* perf: ⚡️ 优化文件搜索结果树模式的显示渲染;优化文件搜索逻辑、减少冗余代码。

---------

Co-authored-by: Sparkle <[email protected]>
  • Loading branch information
6iKUN6 and Sparkle authored Sep 13, 2024
1 parent 29c11a9 commit 88da1c2
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 204 deletions.
3 changes: 1 addition & 2 deletions src/app/edit/[projectId]/(main)/(router)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ const SearchPage: FC = () => {
const openedIds = openedFileIds(models);

useEffect(() => {
// console.log('蔡徐坤');
refreshResult();
}, [
searchInpVal,
Expand Down Expand Up @@ -202,7 +201,7 @@ const SearchPage: FC = () => {
className="cursor-pointer h-full px-[1px] mr-[1px] rounded-[2px] hover:bg-gray-600 duration-75 flex items-center"
onClick={() => handleExpandReplace((prevState) => !prevState)}
>
{expandReplace ? <VscChevronRight /> : <VscChevronDown />}
{expandReplace ? <VscChevronDown /> : <VscChevronRight />}
</span>

<div className="ml-1 flex-1">
Expand Down
4 changes: 2 additions & 2 deletions src/components/fileSearch/matchResult/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const MatchResultItem: FC<{
) : (
<div className="flex items-center relative w-full" onClick={fileClick}>
<span className="mr-[5px]">
{expanded ? <VscChevronRight /> : <VscChevronDown />}
{expanded ? <VscChevronDown /> : <VscChevronRight />}
</span>
<img
src={`/images/fileIcon/${getFileSpecificIcon(filename!)}.svg`}
Expand Down Expand Up @@ -176,7 +176,7 @@ const MatchResultComp: FC<{
list.push({
...match,
...item,
fileId: `${item.fileId}-child${index}`,
fileId: `${item.fileId}-match${index}`,
kind: 'match',
});
});
Expand Down
300 changes: 192 additions & 108 deletions src/components/fileSearch/matchResultTree/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use client';

import { FC, useEffect, useState } from 'react';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { VscChevronDown } from 'react-icons/vsc';
import { editor } from 'monaco-editor';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';

import { useFileSearch } from '@/store/fileSearchStore';
import { TreeMatchResult } from '@/utils/match';
import { addNewModel, cn, getFileLanguage, getFileSpecificIcon } from '@/utils';
import { addNewModel, cn, getFileLanguage, getFileSpecificIcon, RenderedTreeItem } from '@/utils';
import {
useActiveEditorStore,
useActiveModelStore,
Expand All @@ -17,8 +19,9 @@ import {
} from '@/store/editorStore';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';

interface FlattenedTreeMatchResult extends TreeMatchResult {
level: number;
interface AutoSizerProps {
height: number;
width: number;
}

const tipContentClasses = `bg-gray-700 text-while py-1 px-2 border-gray-500 border-[1px]`;
Expand All @@ -28,50 +31,126 @@ const MatchResultTree: FC<{ expandAll: boolean }> = ({ expandAll }) => {
const root = searchResultTree[0];
const treeData = root?.children;

// 拍平树
function flattenTree(tree: TreeMatchResult[], level: number = 0): FlattenedTreeMatchResult[] {
return tree.reduce((acc, node) => {
const newNode = { ...node, level };
const createDefaultExpanded = useCallback(
(treeData: TreeMatchResult[]): Set<string> => {
const ids = new Set<string>();
const addIdsFromTreeData = (data: TreeMatchResult[]) => {
data.forEach((file) => {
ids.add(file.id);

if (node.kind === 'directory' && node.children) {
acc.push(newNode);
acc.push(...flattenTree(node.children, level + 1));
} else if (node.kind === 'file') {
acc.push(newNode);
if (file.kind === 'directory' && file.children && file.children.length) {
addIdsFromTreeData(file.children);
}
});
};

if (treeData) {
addIdsFromTreeData(treeData);
}

return acc;
}, [] as FlattenedTreeMatchResult[]);
}
return ids;
},
[treeData],
);

const [expandedIds, setExpandedIds] = useState<Set<string | undefined>>();

useEffect(() => {
treeData && setExpandedIds(expandAll ? createDefaultExpanded(treeData) : new Set());
}, [treeData, expandAll]);

const expandOrCollapeItem = (id: string): void => {
const newExpandIds = new Set(expandedIds);

if (newExpandIds.has(id)) {
newExpandIds.delete(id);
} else {
newExpandIds.add(id);
}

setExpandedIds(newExpandIds);
};

const createTreeFlattList = useCallback(
(tree: TreeMatchResult[], level: number = 0): RenderedTreeItem[] => {
return tree.reduce((acc, node) => {
const newNode: RenderedTreeItem = {
...node,
level,
kind: node.kind,
};

if (node.kind === 'directory' && node.children) {
acc.push(newNode);
expandedIds?.has(node.id!) && acc.push(...createTreeFlattList(node.children, level + 1));
} else if (node.kind === 'file') {
acc.push(newNode);

const renderedTree = (treeData && flattenTree(treeData)) || [];
if (expandedIds?.has(node.id!) && node.matches) {
node.matches.forEach((match, index) => {
acc.push({
...newNode,
kind: 'match',
...match,
level: level + 1,
id: `${node.id}-match${index}`,
});
});
}
}

return acc;
}, [] as RenderedTreeItem[]);
},
[treeData, expandedIds],
);

const renderedTree = useMemo(() => {
return treeData ? createTreeFlattList(treeData) : [];
}, [treeData, expandedIds]);

const Item: FC<ListChildComponentProps> = ({ index, style }: ListChildComponentProps) => {
const item: RenderedTreeItem = renderedTree[index];

return (
<div className="size-full" key={item.id!} style={style}>
<TreeItem
data={item}
className="text-[14px]"
expandHandle={() => expandOrCollapeItem(item.id!)}
expand={expandedIds?.has(item.id!)}
/>
</div>
);
};

return (
<div>
{renderedTree?.map((data) => {
return (
<div key={data.id}>
<TreeItem data={data} className="text-[14px]" expandProp={expandAll} />
</div>
);
})}
<div className="h-full w-full flex-1">
<AutoSizer>
{({ height, width }: AutoSizerProps) => (
<FixedSizeList
height={height}
width={width}
itemSize={25}
itemCount={renderedTree.length}
className={cn('')}
>
{Item}
</FixedSizeList>
)}
</AutoSizer>
</div>
);
};

const TreeItem: FC<{
data: FlattenedTreeMatchResult;
data: RenderedTreeItem;
className?: string;
expandProp?: boolean;
}> = ({ data, className, expandProp = true }) => {
const { filename, kind, matches, level, id: fileId, value, path } = data;
expand?: boolean;
expandHandle?: (id: string) => void;
}> = ({ data, className, expand, expandHandle }) => {
const { filename, kind, level, id: fileId, value, path, before, after, match, content } = data;
const itemClasses = `cursor-pointer hover:bg-gray-600 px-${level * 5}`;
console.log('data', data);

const [expand, handleExpand] = useState<boolean>(expandProp);
useEffect(() => {
handleExpand(expandProp);
}, [expandProp]);

const { models, setModels } = useModelsStore();
const { activeEditor, activeEditorId } = useActiveEditorStore();
Expand All @@ -91,20 +170,25 @@ const TreeItem: FC<{
mathModel[0].model &&
setModels(
{
id: fileId,
id: fileId!,
filename: mathModel[0].filename,
value: '',
language: getFileLanguage(mathModel[0].filename),
},
mathModel[0].model,
willChangeEditorId,
fileId,
fileId!,
);
willChangeEditor?.setModel(mathModel[0].model);
} else {
const monaco = monacos[willChangeEditorId];
addNewModel(
{ id: fileId, filename, value: value || '', language: getFileLanguage(filename) },
{
id: fileId!,
filename: filename!,
value: value || '',
language: getFileLanguage(filename),
},
monaco as any,
willChangeEditor as editor.IStandaloneCodeEditor,
setModels,
Expand All @@ -114,78 +198,78 @@ const TreeItem: FC<{
}
}

const calculateMatchCount = useCallback((): number => {
let count: number = 0;
const computedMatch = (node: RenderedTreeItem | TreeMatchResult) => {
if (node.kind === 'file') {
count += node.matches?.length || 0;
} else if (node.kind === 'directory') {
node.children?.forEach((child) => {
computedMatch(child);
});
}
};

computedMatch(data);

return count;
}, [data]);

return (
<div className={className}>
{kind === 'directory' ? (
<div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="w-full">
<div
className={cn('flex items-center relative w-full', itemClasses)}
onClick={() => handleExpand(!expand)}
>
<VscChevronDown
className={`mx-1 ${expand ? '' : 'rotate-[270deg]'} duration-200`}
/>
<img src={`/images/fileIcon/FileDir.svg`} className="mr-1" />
{filename}
</div>
</TooltipTrigger>
<TooltipContent className={cn(tipContentClasses, '')}>{path}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
) : kind === 'file' ? (
<div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="w-full">
<div
className={cn('flex items-center relative w-full', itemClasses)}
onClick={() => handleExpand(!expand)}
>
<VscChevronDown
className={`mx-1 ${expand ? '' : 'rotate-[270deg]'} duration-200`}
/>
<img
src={`/images/fileIcon/${getFileSpecificIcon(filename)}.svg`}
className="mr-1"
/>
{filename}
</div>
</TooltipTrigger>
<TooltipContent>{path}</TooltipContent>
</Tooltip>
</TooltipProvider>
<div className={`overflow-hidden ${expand ? 'h-fit' : 'h-0'} duration-200`}>
{matches?.map((match, i) => {
const { match: key, before, after } = match;

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="w-full flex items-center justify-start">
<div
className={cn('truncate w-full text-left', itemClasses)}
key={i}
onClick={findFileAtMatchedSelection}
>
<span className="ml-6">{before}</span>
<span className="bg-green-500/[.4]">{key}</span>
<span>{after}</span>
</div>
</TooltipTrigger>
<TooltipContent className={cn(tipContentClasses, '')}>
{match.content}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})}
</div>
</div>
) : null}
{
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="w-full">
<div className={cn('flex items-center relative w-full', itemClasses)}>
{kind === 'directory' ? (
<span
className="flex items-center w-full"
onClick={() => expandHandle?.(fileId!)}
>
<VscChevronDown
className={`mx-1 ${expand ? '' : 'rotate-[270deg]'} duration-200`}
/>
<img src={`/images/fileIcon/FileDir.svg`} className="mr-1" />
{filename}
</span>
) : kind === 'file' ? (
<span
className="flex items-center w-full"
onClick={() => expandHandle?.(fileId!)}
>
<VscChevronDown
className={`mx-1 ${expand ? '' : 'rotate-[270deg]'} duration-200`}
/>
<img
src={`/images/fileIcon/${getFileSpecificIcon(filename!)}.svg`}
className="mr-1"
/>
{filename}
</span>
) : kind === 'match' ? (
<div
className="text-[14px] cursor-pointer truncate w-full pl-6 ml-[5px] text-left"
onClick={findFileAtMatchedSelection}
>
<span>{before}</span>
<span className="bg-green-500/[.4]">{match}</span>
<span>{after}</span>
</div>
) : null}
{kind !== 'match' && (
<span className="absolute right-2 rounded-full bg-gray-600 text-[10px] text-white px-2">
{calculateMatchCount()}
</span>
)}
</div>
</TooltipTrigger>
<TooltipContent className={cn(tipContentClasses, '')}>
{kind === 'match' ? content : path}
</TooltipContent>
</Tooltip>
</TooltipProvider>
}
</div>
);
};
Expand Down
Loading

0 comments on commit 88da1c2

Please sign in to comment.