优化文件管理器拖拽体验:实现智能跟随tooltip和统一状态管理
- 统一拖拽状态管理:将分散的draggedFiles、dragOverTarget、isDragging等状态合并为单一DragState - 实现跟随鼠标的动态tooltip:实时显示拖拽操作提示,根据目标文件类型智能变化 - 支持三种拖拽模式: * 拖拽到文件夹显示"移动到xxx" * 拖拽到文件显示"与xxx进行diff对比" * 拖拽到空白区域显示"拖到窗口外下载" - 修复边界情况:文件拖拽到自身时忽略操作,避免错误触发 - 使用shadcn设计token统一样式风格 - 移除冗余的DragIndicator组件,简化UI界面 - 添加全局鼠标移动监听,确保tooltip平滑跟随 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,15 @@ import {
|
|||||||
Code,
|
Code,
|
||||||
Settings,
|
Settings,
|
||||||
Download,
|
Download,
|
||||||
|
Upload,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
FileSymlink
|
FileSymlink,
|
||||||
|
Move,
|
||||||
|
GitCompare
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { FileItem } from "../../../types/index.js";
|
import type { FileItem } from "../../../types/index.js";
|
||||||
@@ -46,6 +49,14 @@ function formatFileSize(bytes?: number): string {
|
|||||||
return `${formattedSize} ${units[unitIndex]}`;
|
return `${formattedSize} ${units[unitIndex]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DragState {
|
||||||
|
type: 'none' | 'internal' | 'external';
|
||||||
|
files: FileItem[];
|
||||||
|
target?: FileItem;
|
||||||
|
counter: number;
|
||||||
|
mousePosition?: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
interface FileManagerGridProps {
|
interface FileManagerGridProps {
|
||||||
files: FileItem[];
|
files: FileItem[];
|
||||||
selectedFiles: FileItem[];
|
selectedFiles: FileItem[];
|
||||||
@@ -57,6 +68,7 @@ interface FileManagerGridProps {
|
|||||||
onPathChange: (path: string) => void;
|
onPathChange: (path: string) => void;
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
onUpload?: (files: FileList) => void;
|
onUpload?: (files: FileList) => void;
|
||||||
|
onDownload?: (files: FileItem[]) => void;
|
||||||
onContextMenu?: (event: React.MouseEvent, file?: FileItem) => void;
|
onContextMenu?: (event: React.MouseEvent, file?: FileItem) => void;
|
||||||
viewMode?: 'grid' | 'list';
|
viewMode?: 'grid' | 'list';
|
||||||
onRename?: (file: FileItem, newName: string) => void;
|
onRename?: (file: FileItem, newName: string) => void;
|
||||||
@@ -155,6 +167,7 @@ export function FileManagerGrid({
|
|||||||
onPathChange,
|
onPathChange,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onUpload,
|
onUpload,
|
||||||
|
onDownload,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
viewMode = 'grid',
|
viewMode = 'grid',
|
||||||
onRename,
|
onRename,
|
||||||
@@ -173,13 +186,32 @@ export function FileManagerGrid({
|
|||||||
}: FileManagerGridProps) {
|
}: FileManagerGridProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const gridRef = useRef<HTMLDivElement>(null);
|
const gridRef = useRef<HTMLDivElement>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
|
||||||
const [dragCounter, setDragCounter] = useState(0);
|
|
||||||
const [editingName, setEditingName] = useState('');
|
const [editingName, setEditingName] = useState('');
|
||||||
|
|
||||||
// 拖拽状态管理
|
// 统一拖拽状态管理
|
||||||
const [draggedFiles, setDraggedFiles] = useState<FileItem[]>([]);
|
const [dragState, setDragState] = useState<DragState>({
|
||||||
const [dragOverTarget, setDragOverTarget] = useState<FileItem | null>(null);
|
type: 'none',
|
||||||
|
files: [],
|
||||||
|
counter: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全局鼠标移动监听 - 用于拖拽tooltip跟随
|
||||||
|
useEffect(() => {
|
||||||
|
const handleGlobalMouseMove = (e: MouseEvent) => {
|
||||||
|
if (dragState.type === 'internal' && dragState.files.length > 0) {
|
||||||
|
setDragState(prev => ({
|
||||||
|
...prev,
|
||||||
|
mousePosition: { x: e.clientX, y: e.clientY }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dragState.type === 'internal' && dragState.files.length > 0) {
|
||||||
|
document.addEventListener('mousemove', handleGlobalMouseMove);
|
||||||
|
return () => document.removeEventListener('mousemove', handleGlobalMouseMove);
|
||||||
|
}
|
||||||
|
}, [dragState.type, dragState.files.length]);
|
||||||
|
|
||||||
const editInputRef = useRef<HTMLInputElement>(null);
|
const editInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// 开始编辑时设置初始名称
|
// 开始编辑时设置初始名称
|
||||||
@@ -223,7 +255,13 @@ export function FileManagerGrid({
|
|||||||
const handleFileDragStart = (e: React.DragEvent, file: FileItem) => {
|
const handleFileDragStart = (e: React.DragEvent, file: FileItem) => {
|
||||||
// 如果拖拽的文件已选中,则拖拽所有选中的文件
|
// 如果拖拽的文件已选中,则拖拽所有选中的文件
|
||||||
const filesToDrag = selectedFiles.includes(file) ? selectedFiles : [file];
|
const filesToDrag = selectedFiles.includes(file) ? selectedFiles : [file];
|
||||||
setDraggedFiles(filesToDrag);
|
|
||||||
|
setDragState({
|
||||||
|
type: 'internal',
|
||||||
|
files: filesToDrag,
|
||||||
|
counter: 0,
|
||||||
|
mousePosition: { x: e.clientX, y: e.clientY }
|
||||||
|
});
|
||||||
|
|
||||||
// 设置拖拽数据,添加内部拖拽标识
|
// 设置拖拽数据,添加内部拖拽标识
|
||||||
const dragData = {
|
const dragData = {
|
||||||
@@ -242,8 +280,8 @@ export function FileManagerGrid({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// 只有拖拽到不同文件且不是被拖拽的文件时才设置目标
|
// 只有拖拽到不同文件且不是被拖拽的文件时才设置目标
|
||||||
if (draggedFiles.length > 0 && !draggedFiles.some(f => f.path === targetFile.path)) {
|
if (dragState.type === 'internal' && !dragState.files.some(f => f.path === targetFile.path)) {
|
||||||
setDragOverTarget(targetFile);
|
setDragState(prev => ({ ...prev, target: targetFile }));
|
||||||
e.dataTransfer.dropEffect = 'move';
|
e.dataTransfer.dropEffect = 'move';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -253,8 +291,8 @@ export function FileManagerGrid({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// 清除拖拽目标高亮
|
// 清除拖拽目标高亮
|
||||||
if (dragOverTarget?.path === targetFile.path) {
|
if (dragState.target?.path === targetFile.path) {
|
||||||
setDragOverTarget(null);
|
setDragState(prev => ({ ...prev, target: undefined }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -262,9 +300,18 @@ export function FileManagerGrid({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
setDragOverTarget(null);
|
if (dragState.type !== 'internal' || dragState.files.length === 0) {
|
||||||
|
setDragState(prev => ({ ...prev, target: undefined }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (draggedFiles.length === 0) return;
|
// 检查是否拖拽到自身
|
||||||
|
const isDroppingOnSelf = dragState.files.some(f => f.path === targetFile.path);
|
||||||
|
if (isDroppingOnSelf) {
|
||||||
|
console.log('Ignoring drop on self');
|
||||||
|
setDragState({ type: 'none', files: [], counter: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断拖拽行为:
|
// 判断拖拽行为:
|
||||||
// 1. 文件/文件夹 拖拽到 文件夹 = 移动操作
|
// 1. 文件/文件夹 拖拽到 文件夹 = 移动操作
|
||||||
@@ -273,23 +320,22 @@ export function FileManagerGrid({
|
|||||||
|
|
||||||
if (targetFile.type === 'directory') {
|
if (targetFile.type === 'directory') {
|
||||||
// 移动操作
|
// 移动操作
|
||||||
console.log('Moving files to directory:', draggedFiles.map(f => f.name), 'to', targetFile.name);
|
console.log('Moving files to directory:', dragState.files.map(f => f.name), 'to', targetFile.name);
|
||||||
onFileDrop?.(draggedFiles, targetFile);
|
onFileDrop?.(dragState.files, targetFile);
|
||||||
} else if (targetFile.type === 'file' && draggedFiles.length === 1 && draggedFiles[0].type === 'file') {
|
} else if (targetFile.type === 'file' && dragState.files.length === 1 && dragState.files[0].type === 'file') {
|
||||||
// diff对比操作
|
// diff对比操作
|
||||||
console.log('Comparing files:', draggedFiles[0].name, 'vs', targetFile.name);
|
console.log('Comparing files:', dragState.files[0].name, 'vs', targetFile.name);
|
||||||
onFileDiff?.(draggedFiles[0], targetFile);
|
onFileDiff?.(dragState.files[0], targetFile);
|
||||||
} else {
|
} else {
|
||||||
// 无效操作,给用户提示
|
// 无效操作,给用户提示
|
||||||
console.log('Invalid drag operation');
|
console.log('Invalid drag operation');
|
||||||
}
|
}
|
||||||
|
|
||||||
setDraggedFiles([]);
|
setDragState({ type: 'none', files: [], counter: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileDragEnd = (e: React.DragEvent) => {
|
const handleFileDragEnd = (e: React.DragEvent) => {
|
||||||
setDraggedFiles([]);
|
setDragState({ type: 'none', files: [], counter: 0 });
|
||||||
setDragOverTarget(null);
|
|
||||||
|
|
||||||
// 触发系统级拖拽结束检测
|
// 触发系统级拖拽结束检测
|
||||||
onSystemDragEnd?.(e.nativeEvent);
|
onSystemDragEnd?.(e.nativeEvent);
|
||||||
@@ -360,45 +406,58 @@ export function FileManagerGrid({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// 检查是否是内部文件拖拽
|
// 检查是否是内部文件拖拽
|
||||||
const isInternalDrag = draggedFiles.length > 0; // 如果有内部拖拽的文件,说明是内部拖拽
|
const isInternalDrag = dragState.type === 'internal';
|
||||||
|
|
||||||
if (!isInternalDrag) {
|
if (!isInternalDrag) {
|
||||||
// 只有外部文件拖拽才显示上传提示
|
// 只有外部文件拖拽才显示上传提示
|
||||||
setDragCounter(prev => prev + 1);
|
setDragState(prev => ({
|
||||||
|
...prev,
|
||||||
|
type: 'external',
|
||||||
|
counter: prev.counter + 1
|
||||||
|
}));
|
||||||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
|
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
|
||||||
setIsDragging(true);
|
// External drag detected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [draggedFiles]);
|
}, [dragState.type]);
|
||||||
|
|
||||||
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// 检查是否是内部文件拖拽
|
// 检查是否是内部文件拖拽
|
||||||
const isInternalDrag = draggedFiles.length > 0;
|
const isInternalDrag = dragState.type === 'internal';
|
||||||
|
|
||||||
if (!isInternalDrag) {
|
if (!isInternalDrag && dragState.type === 'external') {
|
||||||
setDragCounter(prev => prev - 1);
|
setDragState(prev => {
|
||||||
if (dragCounter <= 1) {
|
const newCounter = prev.counter - 1;
|
||||||
setIsDragging(false);
|
return {
|
||||||
|
...prev,
|
||||||
|
counter: newCounter,
|
||||||
|
type: newCounter <= 0 ? 'none' : 'external'
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}, [dragState.type, dragState.counter]);
|
||||||
}, [dragCounter, draggedFiles]);
|
|
||||||
|
|
||||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// 检查是否是内部文件拖拽
|
// 检查是否是内部文件拖拽
|
||||||
const isInternalDrag = draggedFiles.length > 0;
|
const isInternalDrag = dragState.type === 'internal';
|
||||||
|
|
||||||
if (isInternalDrag) {
|
if (isInternalDrag) {
|
||||||
|
// 更新鼠标位置
|
||||||
|
setDragState(prev => ({
|
||||||
|
...prev,
|
||||||
|
mousePosition: { x: e.clientX, y: e.clientY }
|
||||||
|
}));
|
||||||
e.dataTransfer.dropEffect = 'move';
|
e.dataTransfer.dropEffect = 'move';
|
||||||
} else {
|
} else {
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
}
|
}
|
||||||
}, [draggedFiles]);
|
}, [dragState.type]);
|
||||||
|
|
||||||
// 滚轮事件处理,确保滚动正常工作
|
// 滚轮事件处理,确保滚动正常工作
|
||||||
const handleWheel = useCallback((e: React.WheelEvent) => {
|
const handleWheel = useCallback((e: React.WheelEvent) => {
|
||||||
@@ -565,22 +624,22 @@ export function FileManagerGrid({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// 检查是否是内部文件拖拽
|
if (dragState.type === 'internal') {
|
||||||
const isInternalDrag = draggedFiles.length > 0;
|
// 内部拖拽到空白区域:触发下载
|
||||||
|
console.log('Internal drag to empty area detected, triggering download');
|
||||||
if (isInternalDrag) {
|
if (onDownload && dragState.files.length > 0) {
|
||||||
// 内部拖拽:不处理,因为已经在 handleFileDrop 中处理了
|
onDownload(dragState.files);
|
||||||
console.log('Internal drag detected, ignoring container drop');
|
}
|
||||||
} else {
|
} else if (dragState.type === 'external') {
|
||||||
// 外部拖拽:处理文件上传
|
// 外部拖拽:处理文件上传
|
||||||
setIsDragging(false);
|
|
||||||
setDragCounter(0);
|
|
||||||
|
|
||||||
if (onUpload && e.dataTransfer.files.length > 0) {
|
if (onUpload && e.dataTransfer.files.length > 0) {
|
||||||
onUpload(e.dataTransfer.files);
|
onUpload(e.dataTransfer.files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [onUpload, draggedFiles]);
|
|
||||||
|
// 重置拖拽状态
|
||||||
|
setDragState({ type: 'none', files: [], counter: 0 });
|
||||||
|
}, [onUpload, onDownload, dragState]);
|
||||||
|
|
||||||
// 文件选择处理
|
// 文件选择处理
|
||||||
const handleFileClick = (file: FileItem, event: React.MouseEvent) => {
|
const handleFileClick = (file: FileItem, event: React.MouseEvent) => {
|
||||||
@@ -815,7 +874,7 @@ export function FileManagerGrid({
|
|||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute inset-0 p-4 overflow-y-auto thin-scrollbar",
|
"absolute inset-0 p-4 overflow-y-auto thin-scrollbar",
|
||||||
isDragging && "bg-blue-500/10 border-2 border-dashed border-blue-500"
|
dragState.type === 'external' && "bg-muted/20 border-2 border-dashed border-primary"
|
||||||
)}
|
)}
|
||||||
onClick={handleGridClick}
|
onClick={handleGridClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
@@ -829,22 +888,37 @@ export function FileManagerGrid({
|
|||||||
onContextMenu={(e) => onContextMenu?.(e)}
|
onContextMenu={(e) => onContextMenu?.(e)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
{isDragging && (
|
{/* 拖拽提示覆盖层 */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-blue-500/10 backdrop-blur-sm z-10 pointer-events-none">
|
{dragState.type === 'external' && (
|
||||||
<div className="text-center">
|
<div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm z-10 pointer-events-none animate-in fade-in-0">
|
||||||
<Download className="w-12 h-12 mx-auto mb-2 text-primary" />
|
<div className="text-center p-8 bg-background/95 border-2 border-dashed border-primary rounded-lg shadow-lg">
|
||||||
<p className="text-lg font-medium text-primary">
|
<Upload className="w-16 h-16 mx-auto mb-4 text-primary" />
|
||||||
{t("fileManager.dragFilesToUpload")}
|
<p className="text-xl font-semibold text-foreground mb-2">
|
||||||
|
拖拽文件到此处上传
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
从系统文件管理器拖拽文件到这里进行上传
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{files.length === 0 ? (
|
{files.length === 0 ? (
|
||||||
<div className="h-full flex items-center justify-center">
|
<div className="h-full flex items-center justify-center p-8">
|
||||||
<div className="text-center text-muted-foreground">
|
<div className="text-center">
|
||||||
<Folder className="w-16 h-16 mx-auto mb-4 opacity-50" />
|
<Folder className="w-16 h-16 mx-auto mb-4 text-muted-foreground/50" />
|
||||||
<p>{t("fileManager.emptyFolder")}</p>
|
<p className="text-lg font-medium text-foreground mb-4">{t("fileManager.emptyFolder")}</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground bg-muted/50 rounded-md px-3 py-2">
|
||||||
|
<Upload className="w-4 h-4" />
|
||||||
|
拖拽系统文件到此处上传
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground bg-muted/50 rounded-md px-3 py-2">
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
拖拽文件到窗口外下载
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : viewMode === 'grid' ? (
|
) : viewMode === 'grid' ? (
|
||||||
@@ -872,8 +946,8 @@ export function FileManagerGrid({
|
|||||||
"group p-3 rounded-lg cursor-pointer transition-all",
|
"group p-3 rounded-lg cursor-pointer transition-all",
|
||||||
"hover:bg-accent hover:text-accent-foreground border-2 border-transparent",
|
"hover:bg-accent hover:text-accent-foreground border-2 border-transparent",
|
||||||
isSelected && "bg-primary/20 border-primary",
|
isSelected && "bg-primary/20 border-primary",
|
||||||
dragOverTarget?.path === file.path && "bg-blue-100 border-blue-400 border-dashed",
|
dragState.target?.path === file.path && "bg-muted border-primary border-dashed",
|
||||||
draggedFiles.some(f => f.path === file.path) && "opacity-50"
|
dragState.files.some(f => f.path === file.path) && "opacity-50"
|
||||||
)}
|
)}
|
||||||
title={`${file.name} - Selected: ${isSelected} - SelectedCount: ${selectedFiles.length}`}
|
title={`${file.name} - Selected: ${isSelected} - SelectedCount: ${selectedFiles.length}`}
|
||||||
onClick={(e) => handleFileClick(file, e)}
|
onClick={(e) => handleFileClick(file, e)}
|
||||||
@@ -957,8 +1031,8 @@ export function FileManagerGrid({
|
|||||||
"flex items-center gap-3 p-2 rounded cursor-pointer transition-all",
|
"flex items-center gap-3 p-2 rounded cursor-pointer transition-all",
|
||||||
"hover:bg-accent hover:text-accent-foreground",
|
"hover:bg-accent hover:text-accent-foreground",
|
||||||
isSelected && "bg-primary/20",
|
isSelected && "bg-primary/20",
|
||||||
dragOverTarget?.path === file.path && "bg-blue-100 border-blue-400 border-dashed",
|
dragState.target?.path === file.path && "bg-muted border-primary border-dashed",
|
||||||
draggedFiles.some(f => f.path === file.path) && "opacity-50"
|
dragState.files.some(f => f.path === file.path) && "opacity-50"
|
||||||
)}
|
)}
|
||||||
onClick={(e) => handleFileClick(file, e)}
|
onClick={(e) => handleFileClick(file, e)}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
@@ -1072,6 +1146,44 @@ export function FileManagerGrid({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 拖拽跟随tooltip */}
|
||||||
|
{dragState.type === 'internal' && dragState.files.length > 0 && dragState.mousePosition && (
|
||||||
|
<div
|
||||||
|
className="fixed z-50 pointer-events-none"
|
||||||
|
style={{
|
||||||
|
left: dragState.mousePosition.x + 16,
|
||||||
|
top: dragState.mousePosition.y - 8
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="bg-background border border-border rounded-md shadow-md px-3 py-2 flex items-center gap-2">
|
||||||
|
{dragState.target ? (
|
||||||
|
dragState.target.type === 'directory' ? (
|
||||||
|
<>
|
||||||
|
<Move className="w-4 h-4 text-blue-500" />
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
移动到 {dragState.target.name}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<GitCompare className="w-4 h-4 text-purple-500" />
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
与 {dragState.target.name} 进行diff对比
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Download className="w-4 h-4 text-green-500" />
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
拖到窗口外下载 ({dragState.files.length} 个文件)
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,6 @@ import { FileWindow } from "./components/FileWindow";
|
|||||||
import { DiffWindow } from "./components/DiffWindow";
|
import { DiffWindow } from "./components/DiffWindow";
|
||||||
import { useDragToDesktop } from "../../../hooks/useDragToDesktop";
|
import { useDragToDesktop } from "../../../hooks/useDragToDesktop";
|
||||||
import { useDragToSystemDesktop } from "../../../hooks/useDragToSystemDesktop";
|
import { useDragToSystemDesktop } from "../../../hooks/useDragToSystemDesktop";
|
||||||
import { DragIndicator } from "../../../components/DragIndicator";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -1275,6 +1274,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
onPathChange={setCurrentPath}
|
onPathChange={setCurrentPath}
|
||||||
onRefresh={() => loadDirectory(currentPath)}
|
onRefresh={() => loadDirectory(currentPath)}
|
||||||
onUpload={handleFilesDropped}
|
onUpload={handleFilesDropped}
|
||||||
|
onDownload={(files) => files.forEach(handleDownloadFile)}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
onRename={handleRenameConfirm}
|
onRename={handleRenameConfirm}
|
||||||
@@ -1322,29 +1322,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
|||||||
onDragToDesktop={() => handleDragToDesktop(contextMenu.files)}
|
onDragToDesktop={() => handleDragToDesktop(contextMenu.files)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 拖拽到桌面指示器 */}
|
|
||||||
<DragIndicator
|
|
||||||
isVisible={
|
|
||||||
dragToDesktop.isDownloading ||
|
|
||||||
dragToDesktop.isDragging ||
|
|
||||||
systemDrag.isDownloading ||
|
|
||||||
systemDrag.isDragging
|
|
||||||
}
|
|
||||||
isDragging={
|
|
||||||
systemDrag.isDragging || dragToDesktop.isDragging
|
|
||||||
}
|
|
||||||
isDownloading={
|
|
||||||
systemDrag.isDownloading || dragToDesktop.isDownloading
|
|
||||||
}
|
|
||||||
progress={
|
|
||||||
systemDrag.isDownloading || systemDrag.isDragging
|
|
||||||
? systemDrag.progress
|
|
||||||
: dragToDesktop.progress
|
|
||||||
}
|
|
||||||
fileName={selectedFiles.length === 1 ? selectedFiles[0]?.name : undefined}
|
|
||||||
fileCount={selectedFiles.length}
|
|
||||||
error={systemDrag.error || dragToDesktop.error}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user