feat: Add sorting functionality to the virtual file system and adapter list methods

This commit is contained in:
shiyu
2025-09-06 16:15:24 +08:00
parent 57919aa7ae
commit 74ffc0bb30
12 changed files with 211 additions and 48 deletions

View File

@@ -27,12 +27,14 @@ export interface SearchResultItem {
}
export const vfsApi = {
list: (path: string, page: number = 1, pageSize: number = 50) => {
list: (path: string, page: number = 1, pageSize: number = 50, sortBy: string = 'name', sortOrder: string = 'asc') => {
const cleaned = path.replace(/\\/g, '/');
const trimmed = cleaned === '/' ? '' : cleaned.replace(/^\/+/, '');
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString()
page_size: pageSize.toString(),
sort_by: sortBy,
sort_order: sortOrder
});
return request<DirListing>(`/fs/${encodeURI(trimmed)}?${params}`);
},

View File

@@ -34,7 +34,7 @@ const FileExplorerPage = memo(function FileExplorerPage() {
const dragCounter = useRef(0);
// --- Hooks ---
const { path, entries, loading, pagination, processorTypes, load, navigateTo, goUp, handlePaginationChange, refresh } = useFileExplorer(navKey);
const { path, entries, loading, pagination, processorTypes, sortBy, sortOrder, load, navigateTo, goUp, handlePaginationChange, refresh, handleSortChange } = useFileExplorer(navKey);
const { selectedEntries, handleSelect, handleSelectRange, clearSelection, setSelectedEntries } = useFileSelection();
const { doCreateDir, doDelete, doRename, doDownload, doShare, doGetDirectLink } = useFileActions({ path, refresh, clearSelection, onShare: (entries) => setSharingEntries(entries), onGetDirectLink: (entry) => setDirectLinkEntry(entry) });
const { appWindows, openFileWithDefaultApp, confirmOpenWithApp, closeWindow, toggleMax, bringToFront, updateWindow } = useAppWindows(path);
@@ -56,8 +56,8 @@ const FileExplorerPage = memo(function FileExplorerPage() {
// --- Effects ---
useEffect(() => {
const routeP = '/' + (restPath || '').replace(/^\/+/, '');
load(routeP, 1, pagination.pageSize);
}, [restPath, navKey, load, pagination.pageSize]);
load(routeP, 1, pagination.pageSize, sortBy, sortOrder);
}, [restPath, navKey, load, pagination.pageSize, sortBy, sortOrder]);
// --- Handlers ---
const handleOpenEntry = (entry: VfsEntry) => {
@@ -136,12 +136,15 @@ const FileExplorerPage = memo(function FileExplorerPage() {
path={path}
loading={loading}
viewMode={viewMode}
sortBy={sortBy}
sortOrder={sortOrder}
onGoUp={goUp}
onNavigate={navigateTo}
onRefresh={refresh}
onCreateDir={() => setCreatingDir(true)}
onUpload={uploader.openModal}
onSetViewMode={setViewMode}
onSortChange={handleSortChange}
/>
<input ref={uploader.fileInputRef} type="file" style={{ display: 'none' }} multiple onChange={uploader.handleFileChange} />

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Flex, Typography, Divider, Button, Space, Tooltip, Segmented, Breadcrumb, Input, theme } from 'antd';
import { ArrowUpOutlined, ReloadOutlined, PlusOutlined, UploadOutlined, AppstoreOutlined, UnorderedListOutlined } from '@ant-design/icons';
import { ArrowUpOutlined, ArrowDownOutlined, ReloadOutlined, PlusOutlined, UploadOutlined, AppstoreOutlined, UnorderedListOutlined } from '@ant-design/icons';
import { Select } from 'antd';
import type { ViewMode } from '../types';
interface HeaderProps {
@@ -8,24 +9,30 @@ interface HeaderProps {
path: string;
loading: boolean;
viewMode: ViewMode;
sortBy: string;
sortOrder: string;
onGoUp: () => void;
onNavigate: (path: string) => void;
onRefresh: () => void;
onCreateDir: () => void;
onUpload: () => void;
onSetViewMode: (mode: ViewMode) => void;
onSortChange: (sortBy: string, sortOrder: string) => void;
}
export const Header: React.FC<HeaderProps> = ({
path,
loading,
viewMode,
sortBy,
sortOrder,
onGoUp,
onNavigate,
onRefresh,
onCreateDir,
onUpload,
onSetViewMode,
onSortChange,
}) => {
const { token } = theme.useToken();
const [editingPath, setEditingPath] = useState(false);
@@ -100,6 +107,22 @@ export const Header: React.FC<HeaderProps> = ({
<Button size="small" icon={<ReloadOutlined />} onClick={onRefresh} loading={loading}></Button>
<Button size="small" icon={<PlusOutlined />} onClick={onCreateDir}></Button>
<Button size="small" icon={<UploadOutlined />} onClick={onUpload}></Button>
<Select
size="small"
value={sortBy}
onChange={(val) => onSortChange(val, sortOrder)}
style={{ width: 80 }}
options={[
{ value: 'name', label: '名称' },
{ value: 'size', label: '大小' },
{ value: 'mtime', label: '修改时间' },
]}
/>
<Button
size="small"
icon={sortOrder === 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
onClick={() => onSortChange(sortBy, sortOrder === 'asc' ? 'desc' : 'asc')}
/>
<Segmented
size="small"
value={viewMode}

View File

@@ -21,14 +21,16 @@ export function useFileExplorer(navKey: string) {
showTotal: (total: number, range: [number, number]) => `${total} 项,第 ${range[0]}-${range[1]}`,
pageSizeOptions: ['20', '50', '100', '200']
});
const [sortBy, setSortBy] = useState('name');
const [sortOrder, setSortOrder] = useState('asc');
const load = useCallback(async (p: string, page: number = 1, pageSize: number = 50) => {
const load = useCallback(async (p: string, page: number = 1, pageSize: number = 50, sb = sortBy, so = sortOrder) => {
const canonical = p === '' ? '/' : (p.startsWith('/') ? p : '/' + p);
setLoading(true);
try {
// Load entries and processor types concurrently
const [res, processors] = await Promise.all([
vfsApi.list(canonical === '/' ? '' : canonical, page, pageSize),
vfsApi.list(canonical === '/' ? '' : canonical, page, pageSize, sb, so),
processorsApi.list()
]);
setEntries(res.entries);
@@ -45,7 +47,7 @@ export function useFileExplorer(navKey: string) {
} finally {
setLoading(false);
}
}, []);
}, [sortBy, sortOrder]);
const navigateTo = useCallback((p: string) => {
const canonical = p === '' || p === '/' ? '/' : (p.startsWith('/') ? p : '/' + p);
@@ -60,23 +62,32 @@ export function useFileExplorer(navKey: string) {
}, [path, navigateTo]);
const handlePaginationChange = (page: number, pageSize: number) => {
load(path, page, pageSize);
load(path, page, pageSize, sortBy, sortOrder);
};
const refresh = () => {
load(path, pagination.current, pagination.pageSize);
load(path, pagination.current, pagination.pageSize, sortBy, sortOrder);
}
const handleSortChange = (sb: string, so: string) => {
setSortBy(sb);
setSortOrder(so);
load(path, 1, pagination.pageSize, sb, so);
};
return {
path,
entries,
loading,
pagination,
processorTypes,
sortBy,
sortOrder,
load,
navigateTo,
goUp,
handlePaginationChange,
refresh,
handleSortChange
};
}