From fe824cb3c21b9f365ebeae185d8ea79f3b9293cd Mon Sep 17 00:00:00 2001 From: gaboolic <23441099@qq.com> Date: Sun, 24 May 2026 19:57:18 +0800 Subject: [PATCH] feat: add file search filter to FileExplorer sidebar --- src/components/FileExplorer.tsx | 94 +++++++++++++++++++++++++++++-- src/locales/en/components.json | 3 +- src/locales/zh-CN/components.json | 3 +- 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx index 11705bfc..f64ffba0 100644 --- a/src/components/FileExplorer.tsx +++ b/src/components/FileExplorer.tsx @@ -9,7 +9,8 @@ import { useTranslation } from 'react-i18next' import { useFileExplorer, type FileTreeNode } from '../hooks' import { useVerticalSplitResize } from '../hooks/useVerticalSplitResize' import { layoutStore, type PreviewFile } from '../store/layoutStore' -import { ChevronRightIcon, ChevronDownIcon, RetryIcon, AlertCircleIcon, DownloadIcon, MaximizeIcon } from './Icons' +import { ChevronRightIcon, ChevronDownIcon, RetryIcon, AlertCircleIcon, DownloadIcon, MaximizeIcon, SearchIcon, CloseIcon } from './Icons' +import { searchFiles } from '../api' import { CodePreview } from './CodePreview' import { FullscreenViewer } from './FullscreenViewer' import { PreviewTabsBar, type PreviewTabsBarItem } from './PreviewTabsBar' @@ -95,6 +96,35 @@ export const FileExplorer = memo(function FileExplorer({ refresh, } = useFileExplorer({ directory, autoLoad: true, sessionId: sessionId || undefined }) + const [filter, setFilter] = useState('') + const [searchResults, setSearchResults] = useState(null) + const [isSearching, setIsSearching] = useState(false) + const searchIdRef = useRef(0) + + useEffect(() => { + const query = filter.trim() + if (!query) { + searchIdRef.current += 1 + setSearchResults(null) + setIsSearching(false) + return + } + const id = ++searchIdRef.current + setIsSearching(true) + ;(async () => { + try { + const results = await searchFiles(query, { directory, limit: 50 }) + if (id !== searchIdRef.current) return + setSearchResults(results) + setIsSearching(false) + } catch { + if (id !== searchIdRef.current) return + setSearchResults([]) + setIsSearching(false) + } + })() + }, [filter, directory]) + // 当 previewFile 改变时加载预览 useEffect(() => { if (previewFile) { @@ -187,16 +217,35 @@ export const FileExplorer = memo(function FileExplorer({ } > {/* Tree Header */} -
- +
+ {t('fileExplorer.explorer')} +
+ + setFilter(e.target.value)} + placeholder={t('fileExplorer.filterFile')} + className="w-full h-5.5 pl-5 pr-5 bg-bg-100 text-text-200 text-[length:var(--fs-xs)] rounded-sm outline-none placeholder:text-text-400/60 border border-border-100/40 focus:border-border-100/70" + /> + {filter && ( + + )} +
+ ) + })} +
+ ) : null + ) : isLoading && tree.length === 0 ? (
{t('common:loading')}
diff --git a/src/locales/en/components.json b/src/locales/en/components.json index 360157ad..04b7dfb8 100644 --- a/src/locales/en/components.json +++ b/src/locales/en/components.json @@ -11,7 +11,8 @@ "failedToLoadFile": "Failed to load file", "binaryFile": "Binary file — preview not available", "fit": "Fit", - "oneToOne": "1:1" + "oneToOne": "1:1", + "filterFile": "Filter files..." }, "terminal": { "terminal": "Terminal", diff --git a/src/locales/zh-CN/components.json b/src/locales/zh-CN/components.json index 3db79681..b6da5501 100644 --- a/src/locales/zh-CN/components.json +++ b/src/locales/zh-CN/components.json @@ -11,7 +11,8 @@ "failedToLoadFile": "加载文件内容失败", "binaryFile": "二进制文件,无法预览", "fit": "适应", - "oneToOne": "1:1" + "oneToOne": "1:1", + "filterFile": "筛选文件..." }, "terminal": { "terminal": "终端",