实现跨边界拖拽功能:支持从浏览器拖拽文件到系统
主要改进: - 使用 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,6 +1,7 @@
|
||||
const { app, BrowserWindow, shell, ipcMain } = require("electron");
|
||||
const { app, BrowserWindow, shell, ipcMain, dialog } = require("electron");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
|
||||
let mainWindow = null;
|
||||
|
||||
@@ -317,8 +318,185 @@ app.on("activate", () => {
|
||||
}
|
||||
});
|
||||
|
||||
// ================== 拖拽功能实现 ==================
|
||||
|
||||
// 临时文件管理
|
||||
const tempFiles = new Map(); // 存储临时文件路径映射
|
||||
|
||||
// 创建临时文件
|
||||
ipcMain.handle("create-temp-file", async (event, fileData) => {
|
||||
try {
|
||||
const { fileName, content, encoding = 'base64' } = fileData;
|
||||
|
||||
// 创建临时目录
|
||||
const tempDir = path.join(os.tmpdir(), 'termix-drag-files');
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 生成临时文件路径
|
||||
const tempId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||
const tempFilePath = path.join(tempDir, `${tempId}-${fileName}`);
|
||||
|
||||
// 写入文件内容
|
||||
if (encoding === 'base64') {
|
||||
const buffer = Buffer.from(content, 'base64');
|
||||
fs.writeFileSync(tempFilePath, buffer);
|
||||
} else {
|
||||
fs.writeFileSync(tempFilePath, content, 'utf8');
|
||||
}
|
||||
|
||||
// 记录临时文件
|
||||
tempFiles.set(tempId, {
|
||||
path: tempFilePath,
|
||||
fileName: fileName,
|
||||
createdAt: Date.now()
|
||||
});
|
||||
|
||||
console.log(`Created temp file: ${tempFilePath}`);
|
||||
return { success: true, tempId, path: tempFilePath };
|
||||
} catch (error) {
|
||||
console.error("Error creating temp file:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 开始拖拽到桌面
|
||||
ipcMain.handle("start-drag-to-desktop", async (event, { tempId, fileName }) => {
|
||||
try {
|
||||
const tempFile = tempFiles.get(tempId);
|
||||
if (!tempFile) {
|
||||
throw new Error("Temporary file not found");
|
||||
}
|
||||
|
||||
// 使用Electron的startDrag API
|
||||
const iconPath = path.join(__dirname, "..", "public", "icon.png");
|
||||
const iconExists = fs.existsSync(iconPath);
|
||||
|
||||
mainWindow.webContents.startDrag({
|
||||
file: tempFile.path,
|
||||
icon: iconExists ? iconPath : undefined
|
||||
});
|
||||
|
||||
console.log(`Started drag for: ${tempFile.path}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error starting drag:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 清理临时文件
|
||||
ipcMain.handle("cleanup-temp-file", async (event, tempId) => {
|
||||
try {
|
||||
const tempFile = tempFiles.get(tempId);
|
||||
if (tempFile && fs.existsSync(tempFile.path)) {
|
||||
fs.unlinkSync(tempFile.path);
|
||||
tempFiles.delete(tempId);
|
||||
console.log(`Cleaned up temp file: ${tempFile.path}`);
|
||||
}
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error cleaning up temp file:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 批量清理过期临时文件(5分钟过期)
|
||||
const cleanupExpiredTempFiles = () => {
|
||||
const now = Date.now();
|
||||
const maxAge = 5 * 60 * 1000; // 5分钟
|
||||
|
||||
for (const [tempId, tempFile] of tempFiles.entries()) {
|
||||
if (now - tempFile.createdAt > maxAge) {
|
||||
try {
|
||||
if (fs.existsSync(tempFile.path)) {
|
||||
fs.unlinkSync(tempFile.path);
|
||||
}
|
||||
tempFiles.delete(tempId);
|
||||
console.log(`Auto-cleaned expired temp file: ${tempFile.path}`);
|
||||
} catch (error) {
|
||||
console.error("Error auto-cleaning temp file:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 每分钟清理一次过期临时文件
|
||||
setInterval(cleanupExpiredTempFiles, 60 * 1000);
|
||||
|
||||
// 创建临时文件夹拖拽支持
|
||||
ipcMain.handle("create-temp-folder", async (event, folderData) => {
|
||||
try {
|
||||
const { folderName, files } = folderData;
|
||||
|
||||
// 创建临时目录
|
||||
const tempDir = path.join(os.tmpdir(), 'termix-drag-folders');
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
|
||||
const tempId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||
const tempFolderPath = path.join(tempDir, `${tempId}-${folderName}`);
|
||||
|
||||
// 递归创建文件夹结构
|
||||
const createFolderStructure = (basePath, fileList) => {
|
||||
for (const file of fileList) {
|
||||
const fullPath = path.join(basePath, file.relativePath);
|
||||
const dirPath = path.dirname(fullPath);
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
if (file.encoding === 'base64') {
|
||||
const buffer = Buffer.from(file.content, 'base64');
|
||||
fs.writeFileSync(fullPath, buffer);
|
||||
} else {
|
||||
fs.writeFileSync(fullPath, file.content, 'utf8');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fs.mkdirSync(tempFolderPath, { recursive: true });
|
||||
createFolderStructure(tempFolderPath, files);
|
||||
|
||||
// 记录临时文件夹
|
||||
tempFiles.set(tempId, {
|
||||
path: tempFolderPath,
|
||||
fileName: folderName,
|
||||
createdAt: Date.now(),
|
||||
isFolder: true
|
||||
});
|
||||
|
||||
console.log(`Created temp folder: ${tempFolderPath}`);
|
||||
return { success: true, tempId, path: tempFolderPath };
|
||||
} catch (error) {
|
||||
console.error("Error creating temp folder:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
console.log("App is quitting...");
|
||||
|
||||
// 清理所有临时文件
|
||||
for (const [tempId, tempFile] of tempFiles.entries()) {
|
||||
try {
|
||||
if (fs.existsSync(tempFile.path)) {
|
||||
if (tempFile.isFolder) {
|
||||
fs.rmSync(tempFile.path, { recursive: true, force: true });
|
||||
} else {
|
||||
fs.unlinkSync(tempFile.path);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error cleaning up temp file on quit:", error);
|
||||
}
|
||||
}
|
||||
tempFiles.clear();
|
||||
});
|
||||
|
||||
app.on("will-quit", () => {
|
||||
|
||||
Reference in New Issue
Block a user