The application made with Electron has a drag-and-drop upload feature, but it is not working. The console reports an error:
Could not get file path from drop event. This indicates a sandbox issue.
Using Cursor + Gemini 2.5 Pro to solve this problem has not been successful. I now want to achieve the ability to drag from the file explorer into the application window for uploading.
Code↓
// electron/main/index.js
// ... (其他 require/import 语句)
ipcMain.handle('get-file-paths', (event, files) => {
// 从预加载脚本收到的文件对象已经是标准数组,
// 并且包含了 'path' 属性。我们只需将其提取出来即可。
return files.map(f => f.path);
});
// ... (应用的其他主进程逻辑)
// electron/preload/index.mjs
import { contextBridge, ipcRenderer } from 'electron'
const api = {
// ... (其他 API 函数)
// Uploads
onUploadProgress: (callback) => {
const handler = (event, ...args) => callback(...args);
ipcRenderer.on('upload-progress', handler);
return () => ipcRenderer.removeListener('upload-progress', handler);
},
getFilePaths: (files) => ipcRenderer.invoke('get-file-paths', files),
// ... (其他 API 函数)
}
contextBridge.exposeInMainWorld('api', api);
// src/components/ui/layout.jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { cn } from '@/lib/utils';
import { UploadCloud } from 'lucide-react';
const Layout = React.forwardRef(({ className, children, ...props }, ref) => {
const [isDragging, setIsDragging] = useState(false);
const navigate = useNavigate();
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.relatedTarget === null) {
setIsDragging(false);
}
};
const handleDrop = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
// 关键一步:将 FileList 转换为标准数组
const droppedFiles = Array.from(e.dataTransfer.files);
if (droppedFiles.length > 0) {
try {
// 调用预加载脚本暴露的 API
const filePaths = await window.api.getFilePaths(droppedFiles);
if (filePaths && filePaths.length > 0) {
// 跳转到上传页面,并传递文件路径
navigate('/uploads', { state: { newFilePaths: filePaths } });
}
} catch (error) {
console.error("Error getting file paths:", error);
}
}
};
return (
<div
ref={ref}
className={cn('h-screen w-full flex relative', className)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
{...props}
>
{children}
{isDragging && (
<div className="absolute inset-0 bg-black bg-opacity-50 flex flex-col items-center justify-center z-50 pointer-events-none">
<UploadCloud className="h-24 w-24 text-white animate-bounce" />
<p className="mt-4 text-2xl font-bold text-white">松开即可上传</p>
</div>
)}
</div>
);
});
Layout.displayName = 'Layout';
// ... (其他布局组件的导出)
export { Layout, /* ... */ };
// src/pages/Uploads.jsx
import { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
// ... (其他 import)
export default function UploadsPage() {
const [filesToUpload, setFilesToUpload] = useState([]);
// ... (其他 state)
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
// 检查路由 state 中是否有新文件路径
if (location.state?.newFilePaths) {
addFilesByPath(location.state.newFilePaths);
// 清理 state,防止刷新页面时重复添加
navigate(location.pathname, { replace: true, state: {} });
}
}, [location, navigate]);
const addFilesByPath = (paths) => {
const newFiles = paths.map(path => {
if (!path || typeof path !== 'string') {
console.error("File path is invalid.");
return null;
};
const key = path.split(/[\\/]/).pop();
if (filesToUpload.find(f => f.path === path)) return null;
return { path, key, status: 'pending' };
}).filter(Boolean);
if (newFiles.length > 0) {
setFilesToUpload(prev => [...prev, ...newFiles]);
}
};
// ... (页面的其余部分,如 handleUpload, JSX 等)
return (
// ...
)
}
Could not get file path from drop event. This indicates a sandbox issue.
This certainly is not an MRE. However, I can tell you what this message likely means.
When you make an Electron app, there are three contexts: main; renderer; preload.
The main context is like a server. It's capable of doing things like reading from the file system.
The renderer context is like a client. It can't read from the file system because it's in a sandbox to prevent just that for security reasons.
The preload context sits between them. Renderer uses preload to get information via Inter-Process Communication (IPC) from main.
The message you posted (quoted at the top here) is telling you that the drop event is in the renderer context and unable to access file paths.
return files.map(f => f.path);
This looks problematic. Let's consider what it's telling us. It thinks that files
contains objects that contain the file path. It is extracting the path from the object and returning it. This is in main context, handling a message from renderer context. I.e. it is expecting the renderer to pass to main an object that contains the file paths. However, if that were possible, you wouldn't need main. Even renderer should be able to iterate over an array and extract a property from each object.
const droppedFiles = Array.from(e.dataTransfer.files);
So this e.dataTransfer.files
is supposed to be some kind of collection of objects. First suggestion, try dropping get-file-paths
and instead just do
const filePaths = Array.from(e.dataTransfer.files).map(f -> f.path);
If that doesn't work, it may give you a more descriptive error message that you can handle better.
Second suggestion. Change the map
.
const filePaths = Array.from(e.dataTransfer.files).map(webUtils.getPathForFile(f));
That's supposed to be the new way and should work in renderer context. If that doesn't work, Gemini may be able to help you fix it. webUtils
.
Third suggestion. Keep get-file-paths
and modify it to use webUtils.getPathForFile
. Instead of f -> f.path
say f -> webUtils.getPathForFile(f)
. Perhaps try this first, as it's the smallest change.
Fourth suggestion. Since writing the original version, I have discovered that it is possible to open a dialog window from main context. If you open your drag-and-drop interface from main, it will return e.dataTransfer.files
to main and it should include the file path.