From c309944494eb2de63bf9b35ea722d50b52e688a3 Mon Sep 17 00:00:00 2001 From: haoyuren <13851610112@163.com> Date: Fri, 13 Mar 2026 16:48:56 -0500 Subject: Add drag-and-drop file upload, fix project creation modal and API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add file upload to Overleaf projects via multipart POST with correct "name" text field (server reads filename from req.body.name, not from Content-Disposition filename) - Add drag-and-drop support in file tree panel with visual feedback - Replace window.prompt() with custom modal for new project creation (prompt() returns null in Electron) - Fix API endpoints: /api/project/new → /project/new Co-Authored-By: Claude Opus 4.6 --- src/renderer/src/App.css | 5 +++ src/renderer/src/App.tsx | 11 ++++++ src/renderer/src/components/FileTree.tsx | 52 ++++++++++++++++++++++++++++- src/renderer/src/components/ProjectList.tsx | 37 +++++++++++++++++--- 4 files changed, 99 insertions(+), 6 deletions(-) (limited to 'src/renderer') diff --git a/src/renderer/src/App.css b/src/renderer/src/App.css index 63dc8bf..c6d8126 100644 --- a/src/renderer/src/App.css +++ b/src/renderer/src/App.css @@ -303,6 +303,11 @@ html, body, #root { background: var(--bg-secondary); user-select: none; } +.file-tree-dragover { + outline: 2px dashed #4ECDA0; + outline-offset: -2px; + background: rgba(78, 205, 160, 0.08); +} .file-tree-header { display: flex; diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index df9e251..869bdae 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -53,6 +53,17 @@ export default function App() { const [checkingSession, setCheckingSession] = useState(true) + // Prevent Electron from navigating to dropped files + useEffect(() => { + const prevent = (e: DragEvent) => e.preventDefault() + document.addEventListener('dragover', prevent) + document.addEventListener('drop', prevent) + return () => { + document.removeEventListener('dragover', prevent) + document.removeEventListener('drop', prevent) + } + }, []) + // Check session on startup useEffect(() => { window.api.overleafHasWebSession().then(({ loggedIn }) => { diff --git a/src/renderer/src/components/FileTree.tsx b/src/renderer/src/components/FileTree.tsx index 0dda560..a95b0c3 100644 --- a/src/renderer/src/components/FileTree.tsx +++ b/src/renderer/src/components/FileTree.tsx @@ -263,6 +263,51 @@ export default function FileTree() { closeMenu() } + const [dragOver, setDragOver] = useState(false) + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setDragOver(true) + }, []) + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setDragOver(false) + }, []) + + const handleDrop = useCallback(async (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setDragOver(false) + + const projectId = useAppStore.getState().overleafProjectId + const folderId = useAppStore.getState().rootFolderId + if (!projectId || !folderId) return + + const droppedFiles = e.dataTransfer.files + if (droppedFiles.length === 0) return + + for (let i = 0; i < droppedFiles.length; i++) { + const file = droppedFiles[i] + const filePath = window.api.getPathForFile(file) + const fileName = file.name + + useAppStore.getState().setStatusMessage(`Uploading ${fileName}...`) + const result = await window.api.uploadFileToProject(projectId, folderId, filePath, fileName) + if (result.success) { + useAppStore.getState().setStatusMessage(`Uploaded ${fileName}`) + } else { + useAppStore.getState().setStatusMessage(`Upload failed: ${result.message}`) + return + } + } + + // Refresh file tree + await reconnectProject(projectId) + }, []) + const handleOpenInOverleaf = () => { const projectId = useAppStore.getState().overleafProjectId if (projectId) { @@ -272,7 +317,12 @@ export default function FileTree() { } return ( -
+
FILES
diff --git a/src/renderer/src/components/ProjectList.tsx b/src/renderer/src/components/ProjectList.tsx index a3c9c37..58a6bb0 100644 --- a/src/renderer/src/components/ProjectList.tsx +++ b/src/renderer/src/components/ProjectList.tsx @@ -30,6 +30,8 @@ export default function ProjectList({ onOpenProject }: Props) { const [busyText, setBusyText] = useState('') const [sortBy, setSortBy] = useState('lastUpdated') const [sortOrder, setSortOrder] = useState('desc') + const [showNewProject, setShowNewProject] = useState(false) + const [newProjectName, setNewProjectName] = useState('Untitled Project') const { setStatusMessage } = useAppStore() const loadProjects = useCallback(async () => { @@ -76,18 +78,20 @@ export default function ProjectList({ onOpenProject }: Props) { } const handleCreateProject = async () => { - const name = prompt('Project name:', 'Untitled Project') - if (!name?.trim()) return + const name = newProjectName.trim() + if (!name) return + setShowNewProject(false) setError('') setBusy(true) setBusyText('Creating project...') - const result = await window.api.overleafCreateProject(name.trim()) + const result = await window.api.overleafCreateProject(name) setBusy(false) if (result.success && result.projectId) { - setStatusMessage(`Created "${name.trim()}"`) + setStatusMessage(`Created "${name}"`) + setNewProjectName('Untitled Project') loadProjects() } else { setError(result.message || 'Failed to create project') @@ -225,7 +229,7 @@ export default function ProjectList({ onOpenProject }: Props) { placeholder="Search projects..." autoFocus /> -
+ {showNewProject && ( +
setShowNewProject(false)}> +
e.stopPropagation()}> +

New Project

+ setNewProjectName(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') handleCreateProject() + if (e.key === 'Escape') setShowNewProject(false) + }} + autoFocus + style={{ width: '100%', marginBottom: 12 }} + /> +
+ + +
+
+
+ )}
) } -- cgit v1.2.3