Major UI/UX improvements to replace clunky sidebar with modern grid layout: - Add FileManagerModern component with grid-based file browser - Implement drag-and-drop file upload with validation and progress - Add comprehensive context menu with file operations (copy/cut/paste/delete) - Create intelligent file selection system with multi-select support - Add modern toolbar with search, view switching, and file operations - Integrate seamless view switching between classic and modern interfaces - Support keyboard shortcuts and accessibility features - Add complete i18n support for all new interface elements Technical components: - FileManagerGrid: Grid layout with breadcrumb navigation - FileManagerContextMenu: Right-click context menu system - useFileSelection: Hook for managing file selection state - useDragAndDrop: Hook for handling drag-and-drop operations - View switching logic integrated into main FileManager component The modern interface is now the default while maintaining backward compatibility. Users can switch between modern and classic views seamlessly.
82 lines
2.1 KiB
TypeScript
82 lines
2.1 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
|
|
interface FileItem {
|
|
name: string;
|
|
type: "file" | "directory" | "link";
|
|
path: string;
|
|
size?: number;
|
|
modified?: string;
|
|
permissions?: string;
|
|
owner?: string;
|
|
group?: string;
|
|
}
|
|
|
|
export function useFileSelection() {
|
|
const [selectedFiles, setSelectedFiles] = useState<FileItem[]>([]);
|
|
|
|
const selectFile = useCallback((file: FileItem, multiSelect = false) => {
|
|
if (multiSelect) {
|
|
setSelectedFiles(prev => {
|
|
const isSelected = prev.some(f => f.path === file.path);
|
|
if (isSelected) {
|
|
return prev.filter(f => f.path !== file.path);
|
|
} else {
|
|
return [...prev, file];
|
|
}
|
|
});
|
|
} else {
|
|
setSelectedFiles([file]);
|
|
}
|
|
}, []);
|
|
|
|
const selectRange = useCallback((files: FileItem[], startFile: FileItem, endFile: FileItem) => {
|
|
const startIndex = files.findIndex(f => f.path === startFile.path);
|
|
const endIndex = files.findIndex(f => f.path === endFile.path);
|
|
|
|
if (startIndex !== -1 && endIndex !== -1) {
|
|
const start = Math.min(startIndex, endIndex);
|
|
const end = Math.max(startIndex, endIndex);
|
|
const rangeFiles = files.slice(start, end + 1);
|
|
setSelectedFiles(rangeFiles);
|
|
}
|
|
}, []);
|
|
|
|
const selectAll = useCallback((files: FileItem[]) => {
|
|
setSelectedFiles([...files]);
|
|
}, []);
|
|
|
|
const clearSelection = useCallback(() => {
|
|
setSelectedFiles([]);
|
|
}, []);
|
|
|
|
const toggleSelection = useCallback((file: FileItem) => {
|
|
setSelectedFiles(prev => {
|
|
const isSelected = prev.some(f => f.path === file.path);
|
|
if (isSelected) {
|
|
return prev.filter(f => f.path !== file.path);
|
|
} else {
|
|
return [...prev, file];
|
|
}
|
|
});
|
|
}, []);
|
|
|
|
const isSelected = useCallback((file: FileItem) => {
|
|
return selectedFiles.some(f => f.path === file.path);
|
|
}, [selectedFiles]);
|
|
|
|
const getSelectedCount = useCallback(() => {
|
|
return selectedFiles.length;
|
|
}, [selectedFiles]);
|
|
|
|
return {
|
|
selectedFiles,
|
|
selectFile,
|
|
selectRange,
|
|
selectAll,
|
|
clearSelection,
|
|
toggleSelection,
|
|
isSelected,
|
|
getSelectedCount,
|
|
setSelectedFiles
|
|
};
|
|
} |