实现跨边界拖拽功能:支持从浏览器拖拽文件到系统
主要改进: - 使用 File System Access API 实现真正的跨应用边界文件传输 - 支持拖拽到窗口外自动触发系统保存对话框 - 智能路径记忆功能,记住用户上次选择的保存位置 - 多文件自动打包为 ZIP 格式 - 现代浏览器优先使用新 API,旧浏览器降级到传统下载 - 完整的视觉反馈和进度显示 技术实现: - 新增 useDragToSystemDesktop hook 处理系统级拖拽 - 扩展 Electron 主进程支持拖拽临时文件管理 - 集成 JSZip 库支持多文件打包 - 使用 IndexedDB 存储用户偏好的保存路径 - 优化文件管理器拖拽事件处理链 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { FileManagerGrid } from "./FileManagerGrid";
|
||||
import { FileManagerContextMenu } from "./FileManagerContextMenu";
|
||||
import { useFileSelection } from "./hooks/useFileSelection";
|
||||
@@ -6,6 +6,9 @@ import { useDragAndDrop } from "./hooks/useDragAndDrop";
|
||||
import { WindowManager, useWindowManager } from "./components/WindowManager";
|
||||
import { FileWindow } from "./components/FileWindow";
|
||||
import { DiffWindow } from "./components/DiffWindow";
|
||||
import { useDragToDesktop } from "../../../hooks/useDragToDesktop";
|
||||
import { useDragToSystemDesktop } from "../../../hooks/useDragToSystemDesktop";
|
||||
import { DragIndicator } from "../../../components/DragIndicator";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { toast } from "sonner";
|
||||
@@ -110,6 +113,18 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
||||
maxFileSize: 100 // 100MB
|
||||
});
|
||||
|
||||
// 拖拽到桌面功能
|
||||
const dragToDesktop = useDragToDesktop({
|
||||
sshSessionId: sshSessionId || '',
|
||||
sshHost: currentHost!
|
||||
});
|
||||
|
||||
// 系统级拖拽到桌面功能(新方案)
|
||||
const systemDrag = useDragToSystemDesktop({
|
||||
sshSessionId: sshSessionId || '',
|
||||
sshHost: currentHost!
|
||||
});
|
||||
|
||||
// 初始化SSH连接
|
||||
useEffect(() => {
|
||||
if (currentHost) {
|
||||
@@ -124,6 +139,41 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
||||
}
|
||||
}, [sshSessionId, currentPath]);
|
||||
|
||||
// 文件拖拽到外部处理
|
||||
const handleFileDragStart = useCallback((files: FileItem[]) => {
|
||||
// 记录当前拖拽的文件
|
||||
systemDrag.startDragToSystem(files, {
|
||||
enableToast: true,
|
||||
onSuccess: () => {
|
||||
clearSelection();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('拖拽失败:', error);
|
||||
}
|
||||
});
|
||||
}, [systemDrag, clearSelection]);
|
||||
|
||||
const handleFileDragEnd = useCallback((e: DragEvent) => {
|
||||
// 检查是否拖拽到窗口外
|
||||
const margin = 50;
|
||||
const isOutside = (
|
||||
e.clientX < margin ||
|
||||
e.clientX > window.innerWidth - margin ||
|
||||
e.clientY < margin ||
|
||||
e.clientY > window.innerHeight - margin
|
||||
);
|
||||
|
||||
if (isOutside) {
|
||||
// 延迟执行,避免与其他事件冲突
|
||||
setTimeout(() => {
|
||||
systemDrag.handleDragEnd(e);
|
||||
}, 100);
|
||||
} else {
|
||||
// 取消拖拽
|
||||
systemDrag.cancelDragToSystem();
|
||||
}
|
||||
}, [systemDrag]);
|
||||
|
||||
async function initializeSSHConnection() {
|
||||
if (!currentHost) return;
|
||||
|
||||
@@ -1055,6 +1105,39 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
||||
toast.success(`正在对比文件: ${file1.name} 与 ${file2.name}`);
|
||||
}
|
||||
|
||||
// 拖拽到桌面处理函数
|
||||
async function handleDragToDesktop(files: FileItem[]) {
|
||||
if (!currentHost || !sshSessionId) {
|
||||
toast.error(t("fileManager.noSSHConnection"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 优先使用新的系统级拖拽方案
|
||||
if (systemDrag.isFileSystemAPISupported) {
|
||||
await systemDrag.handleDragToSystem(files, {
|
||||
enableToast: true,
|
||||
onSuccess: () => {
|
||||
console.log('系统级拖拽成功');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('系统级拖拽失败:', error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 降级到Electron方案
|
||||
if (files.length === 1) {
|
||||
await dragToDesktop.dragFileToDesktop(files[0]);
|
||||
} else if (files.length > 1) {
|
||||
await dragToDesktop.dragFilesToDesktop(files);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('拖拽到桌面失败:', error);
|
||||
toast.error(`拖拽失败: ${error.message || '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤文件并添加新建的临时项目
|
||||
let filteredFiles = files.filter(file =>
|
||||
file.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
@@ -1205,6 +1288,8 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
||||
onUndo={handleUndo}
|
||||
onFileDrop={handleFileDrop}
|
||||
onFileDiff={handleFileDiff}
|
||||
onSystemDragStart={handleFileDragStart}
|
||||
onSystemDragEnd={handleFileDragEnd}
|
||||
/>
|
||||
|
||||
{/* 右键菜单 */}
|
||||
@@ -1234,6 +1319,31 @@ function FileManagerContent({ initialHost, onClose }: FileManagerModernProps) {
|
||||
onNewFile={handleCreateNewFile}
|
||||
onRefresh={() => loadDirectory(currentPath)}
|
||||
hasClipboard={!!clipboard}
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user